Arc Forumnew | comments | leaders | submitlogin
Some macro confusion
4 points by mpr 3168 days ago | 1 comment
I have a macro question that needs a bit of context. I'm attempting to build an object system around closures, translating some code from Doug Hoyte's Let Over Lambda book [1]. The following macro, dlambda, is given in his book as a macro that creates a lambda function containing possibly multiple closures. The lambda function will decide which closure to execute based on the argument given at evaluation time. I figure this is a pretty cool way to hold state and even define an "object" abstraction. Here is the macro and an example of using it:

    (mac dlambda ds
      (w/uniq args
          `(fn ,args
             (case (car ,args)
               ,@(mappend
                   (fn (d)
                     `(,(if (is t (car d))
                          t
                          (car d))
                        (apply (fn ,@(cdr d))
                               ,(if (is t (car d))
                                  args
                                  `(cdr ,args)))))
                   ds)))))

    (= matt (with (name "matt" age 21)
              (dlambda
                (name x
                      (if (no x)
                        name
                        (= name (car x))))
                (age x
                     (if (no x)
                       age
                       (= age (car x))))
                (say words
                     (prn (string words))))))

    (matt 'age)
    ; => 21

    (matt 'name "bob")
    ; => "bob"
    (matt 'name)
    ; => "bob"

    (matt 'say "hello, " "sir")
    ; => hello, sir
And now I would like to wrap this closure creating ability into a macro that creates macros for creating these closure objects given a class name and a list of attributes. Here is my best attempt so far:

    (def attrfn (attr)
      "this takes an attribute name and creates a getter/setter
      that can be passed to dlambda. Ex:
      arc> (attrfn 'name)
      (name x
        (if (no x)
          name
          (= name (car x))))"
      (w/uniq arg
        `(,attr ,arg
                (if (no ,arg)
                  ,attr
                  (= ,attr (car ,arg))))))

    (mac defobject (class attrs)
      (with (letargs (mappend [list _ (list 'unquote _)] attrs)
             attrfns (map [attrfn _] attrs))
        (w/uniq var
        `(mac ,class (,var ,attrs)
           `(= ,,var (with ,',letargs)
               (dlambda
                 ,',@attrfns))))))
A call to (defobject user (name age)) will generate this 'user' macro:

    arc> (defobject user (name age))
    arc> (src user)

    (mac user (var62747 (name age))
      `(= 
         ,var62747
         (with ,'(name ,name age ,age))
         (dlambda 
           ,'(name arg62745
                   (if (no arg62745)
                     name
                     (= name (car arg62745)))))))
And finally, trying to create a user will fail like this:

    (user matt ("matt" 21))
    _unquote: undefined;
     cannot reference an identifier before its definition
      in module: "/Users/matt/code/arc/anarki/ac.scm"
      context...:
       /Users/matt/code/arc/anarki/ac.scm:1260:4
I know I have been advised (even on this forum) to avoid nested backquotes, but in a macro defining macro, I don't see another way. Thanks in advance for any help!

[1] http://letoverlambda.com/

Edit: To be clear, I'm not sure if this is something I can fix directly in my code, or if I should approach the problem another way.



2 points by rocketnia 3168 days ago | link

I bet the error is coming from here:

  `(=
     ...
     (with ,'(name ,name age ,age))
     ...)
There's an unquote inside another unquote there, so it's actually trying to do a function call (unquote name), which doesn't work because `unquote` is undefined. There's also the problem that your (with ...) body is sitting outside the (with ...) form.

A small fix would be to generate this instead:

  `(=
     ...
     (with ,`(name ,name age ,age)
       ...))
Here's a patch for that:

   (mac defobject (class attrs)
     (with (letargs (mappend [list _ (list 'unquote _)] attrs)
            attrfns (map [attrfn _] attrs))
       (w/uniq var
       `(mac ,class (,var ,attrs)
  -       `(= ,,var (with ,',letargs)
  -           (dlambda
  -             ,',@attrfns))))))
  +       `(= ,,var
  +           (with ,(list 'quasiquote ,letargs)
  +             (dlambda
  +               ,',@attrfns)))))))
I haven't read your code very closely or tested whether this change makes it work, so there might be other bugs in there.

-----