Arc Forumnew | comments | leaders | submitlogin
6 points by kennytilton 3878 days ago | link | parent

(a) No apologies needed, I kinda anticipated this question. :) (b) Perhaps the best use case was just added, the application of DSB to DEFUN. I should extend the examples, tho. (c) In simple cases where one bit of application generates a result in the form of a list of values, native Arc destructuring such as:

  (let (x y . z) data-genned-elsewhere ...) fine, and indeed is the most common case. But as the application becomes more elaborate we might start having one bit of code emitting data of a more interesting nature, and then at the point of consumption we want to be able to parse that data tersely and in a self-documenting way.

So I might have an application where RGB triples are being passed around and eventually handed to OpenGL and app code just emits (list r g b) and the code that talks to OpenGL looks like:

   (dsb (r g b) color-data
      ...tell ogl about r g and b ...)
Fine, but now I decide in one place to emit an alpha value as well. No problem, all the code just emitting RGB stays unchanged, but in one place I can emit RGBA and then modify the consuming location thus:

   (dsb (r g b &o a) color-data
If I now want to get into shininess or material or other ogl options, the optional thing gets nuts because I have to remember in which position shininess goes and then make sure I supply enough nils for other optionals to get the right alignment, and that breaks if I have a fun default for one of the optionals I am bogusly providing just to get alignment. keyword args get us out of that trap, and we do not even have to list them in the right order, they get worked out at call time (so, yes, do not abuse this feature).

  (dsb (r g b &k (a 255) shiny mat) color-data
     ... etc)
Now in CL we have DEFSTRUCT and can at some point punt on lists and get into emitting:

   (make-coloring :r 42 :g 42 :b 42 :shiny +gl-shiny-super+)
...and then the consumer reads the structure attributes so it can talk to OpenGL. The Arc DEFTEM is a solution here if we are restricting ourselves to keyword args. And as I said at the outset, a CL-style DEFUN might be the posterboy for DSB, because we can now decide a DEF needs a new parameter and add it as an optional or keyword arg without changing every call. And where an especially hairy DEF such as open-file will end up with a kazillion parameters, keyword args make calling the function a lot slicker.

1 point by NickSmith 3878 days ago | link

Thanks Kenny, that hit the Mark :)

Looking at mac dsb I realise just how much I have to learn :(


4 points by kennytilton 3878 days ago | link

heh-heh, don't feel bad, i was not kidding when I said it is one of the craziest macros I ever wrote, and I have written over five hundred. Might be the craziest, actually. Macros are normally hard because we are writing code to write code, bouncing back and forth between the mindset of the expanding code and that of the expanded code we are after as we go from backquote to unquote and back.

But in this case with the keyword args the expanded code then had to include code to continue the binding process, because the expander code cannot guess which keywords had been supplied by the caller. Hence the rather sick stop mid-expansion to produce the kvs binding with remaining runtime args paired into an assoc before continuing on to produce the other keyword bindings, each necessitating a runtime lookup of the keyword before deciding if any default should be used, which default might itself still be code!

Man, I now I am confused. Good thing I wrote the macro before I wrote this comment. :)

Needless to say, the macro was evolved: first just getting required params to work. Trivial, but still. Then optionals, with defaults, including computed defaults. I had a bug here, recall, forgetting nil supplied in an optional position means nil, not the default. Then keyword args.

A bit of a giveaway, btw, is my apology above for leaving behind some print statements in the expanding code. We debug macros just like we debug any other code.

Another tip is that I might have broken out that little state machine in the beginning as a separate function to parse the params like:

  (a b c &o (d 42) (e (+ c 1)) &k x) 
...into an assoc:

   '((reqs a b c)(opts (d 42)(e (+ c 1))(keys x))
ie, Divide and conquer: get that working separately, then tackle the rest. I did not do that because of insufficient lookahead (did not realize what I got myself into). :)