Reflection Vs Code Generation Article

(http://www.javaworld.com/javaworld/jw-11-2001/jw-1102-codegen.html) An article by MikeRettig and MartinFowler espouses code generation over reflection. The article provides me with food for thought, but seems to merely be an attack on JavaReflection?, and my experiences of JavaReflection? have not been bad. I believe that the structure they propose is probably good for producting simple value objects from XML documents, but it would seem to get a lot hairier once canonicalization, relations and dependencies between elements need to be considered, and that the CodeGenerator would soon balloon into a behemoth. I may be being unfair, and will have a look at the source code and think about it some more. On a side note, this seems somewhat similar to Sun's ProjectAdelard - perhaps an even simpler thing that would work? -- ShaunSmith

As author of the article, I know for certain it is not an attack on reflection. The CodeGenerator provided as an example relies on reflection. Also, a solution that relies solely on reflection generally will become an over complex behemoth much faster than a similar solution with a CodeGenerator. -- MikeRettig

I wonder if your choice of environment had something to do with your decision to use code generation over reflection. In VisualAgeJava generating Java code is nearly impossible, but I've had great success using reflection to solve similar problems with VAJ. -- MarkAddleman

I agree that environment is important, as is language. One of the benefits cited in the article is the detection of errors at build-time instead of run-time. Clearly this requires some level of static checking. So Smalltalk users may not see this specific benefit. However, you can always have your code generator validate its input. Also, code generation does require an environment that encourages cooperation between processes. I use an IDE known as "Unix". This is very flexible because it is largely based on the paradigm of processes that transform text files. "make" does have a weakness when you want to generate multiple files with one script, but that restriction becomes one of the forces that guide the design of the software system. -- DaveWhipp


[This article came up in the discussion of CodeGenerationIsaDesignSmell.]


sooooooooooo... does this mean LispMacros could all be replaced by LispFunctions?, with sufficient reflection ability? Besides the obvious and frankly quite uninteresting TuringEquivalent ... which is about as interesting as saying any two numbers have a common multiple :) -- WilliamUnderwood


Yes, provided that you have sufficient reflection capabilities to hack into the evaluation mechanism itself. Early Lisps allowed that: there were special types of functions (called FEXPRs, I think) which got their arguments unevaluated and could then call "eval" themselves, if they liked. This is however almost impossible to compile, and the semantics get rather, uh, interesting when such functions are combined with HigherOrderFunctions. Macros were invented later as a more efficient and controlled way to get similar capabilities.


KernelLanguage is a ModernCleanLisp that defines functions in terms of macros rather than the other way around. It uses a lambda-like form $vau that represents an anonymous macro, and provides a wrap operation that takes a macro and returns a function. Lambda is still available as $lambda but is no longer primitive. (The $ is a convention to distinguish special forms from functions.) -- JeffreyHantin

Functions in terms of macros sounds interesting and rather unique. But, upon thinking about it, I can identify some significant disadvantages. The primary one is that macros lack intrinsic semantics. A macro's meaning and semantics depends ultimately upon the context in which it is applied; thus, you can't really have 'first class macros' because every time you pass the macro around (to a different context), it suddenly means (and thus 'is') something different. That would be horrible in distributed systems programming, and could be rather difficult to manage in other programs. OTOH, an operation on syntax that designates when to capture the semantics would fix this problem; if that is what the KernelLanguage 'wrap' operation does, then it wouldn't be bad.


From what I could tell by skimming the PDF on it, KernelLanguage's $vau operator works analogously to how IoLanguage works. Or, more specifically, IoLanguage beat KernelLanguage to the punch.

In Io, ALL evaluations are done in call-by-abstract-syntax-tree style. That means, there is zero distinction between a method, a block, and a macro. The body of a method (say) is ultimately responsible for manually evaluating its parameters if it so desires. In this way, the if() function is, really, a true, honest to goodness function, as is while(), etc.

To make coding a bit easier, however, IoLanguage incorporates a nice "sugar" where the first x variables to a method (or whatever) are eagerly evaluated for you. So you can do stuff like:

  /* Called: ifEquals(a,b,C,D)

if a==b, evaluate C and return its results. Otherwise, D.

Notice how a and b are mapped to formal parameters; these are eagerly evaluated by Io as a convenience. (You may still access the ASTs directly using "message argAt()" if you wish). All other parameters are unbound to variables, but via the argAt() method, you can access their ASTs for manual evaluation. We exploit this in the silly example below. */ ifEquals := method(s1, s2, if(s1 == s2, message argAt(2) evaluate, message argAt(3) evaluate) )

I forget the precise message, but there is a message which allows you to evaluate either in the context of ifEquals(), in a totally new context, or in the caller's own context. I'm going to magically assume the evaluate message knows what I mean here, but be aware that this has to be specified in real-world IoLanguage code.

This can be invoked like so:

  ifEquals(x, y,  /* These are pre-eval'ed since they were expressed in the method definition. */

/* Then... */ x := "No way" x print, /* <-- note comma -- ASTs are parameters! */

/* Else... */ x := "I thought so." x print "For a moment, I thought I was going crazy.")

What Lispers know as macros is implemented by literally altering the AST that is passed into the function directly. I'd show an example, but I totally forget how to do that. Someone with more recent Io experience than I can probably fill this in. It is, nonetheless, fairly rarely used.

--SamuelFalvo?


CategoryReflection


EditText of this page (last edited July 9, 2010) or FindPage with title or text search