Metaprogramming: Difference between revisions
Fixed typos. |
|||
Line 58: | Line 58: | ||
* E program fragments may be quoted, manipulated as an AST, and evaluated, similarly to Lisp; lexical environments are first-class objects (though static with respect to the evaluated code). Demonstrated in [[Runtime evaluation#E]] and [[Eval in environment#E]]. |
* E program fragments may be quoted, manipulated as an AST, and evaluated, similarly to Lisp; lexical environments are first-class objects (though static with respect to the evaluated code). Demonstrated in [[Runtime evaluation#E]] and [[Eval in environment#E]]. |
||
* Control structures may be defined, as demonstrated in [[Extend your language#E]]. |
* Control structures may be defined, as demonstrated in [[Extend your language#E]]. |
||
=={{header|Common Lisp}}== |
|||
We can use Lisp macros, and other features, to add support to Lisp for monads, which come from functional languages. The following module of code provides a new macro form called COMPREHEND which works with monads. If we use the LIST monad, we get list comprehensions. For instance: |
|||
<lang lisp> |
|||
;; The -> notation is not part of Lisp, it is used in examples indicate the output of a form. |
|||
;; |
|||
;; |
|||
(comprehend 'list-monad (cons x y) (x '(1 2 3)) (y '(A B C))) |
|||
-> ((1 . A) (1 . B) (1 . C) |
|||
(2 . A) (2 . B) (2 . C) |
|||
(3 . A) (3 . B) (3 . C))</lang> |
|||
As you can see, the comprehension processes all combinations of X and Y from both sets, and collects the application of (CONS X Y) to these elements. |
|||
In other words {(cons x y) | x ∈ { 1, 2 ,3 } ∧ y ∈ { A, B, C }} |
|||
Other monads are possible: idenitity, state transfomer, etc. Some of these are provided in the code below. |
|||
Furthermore, a form called DEFINE-MONAD is provided to define new kinds of monads. It is used to define the basic monads. DEFINE-MONAD also optionally generates a (trivial) short-hand comprehension macro for your monad type. So instead of (comprehend 'list ...) it is possible to write is also (list-comp ...). |
|||
Note how the state transformer monad uses the identity monad comprehension in its definition. |
|||
Also, a monad is a class, and there is a way in the DEFINE-MONAD syntax to declare what the base classes are (multiple inheritance) as well as any additional custom slots. |
|||
To see the original version of this code with lengthy comments, have a look in the Lisp Pastebin. http://paste.lisp.org/display/71196 |
|||
<lang lisp>(defgeneric monadic-map (monad-class function)) |
|||
(defgeneric monadic-join (monad-class container-of-containers &rest additional)) |
|||
(defgeneric monadic-instance (monad-class-name)) |
|||
(defmacro comprehend (monad-instance expr &rest clauses) |
|||
(let ((monad-var (gensym "CLASS-"))) |
|||
(cond |
|||
((null clauses) `(multiple-value-call #'monadic-unit |
|||
,monad-instance ,expr)) |
|||
((rest clauses) `(let ((,monad-var ,monad-instance)) |
|||
(multiple-value-call #'monadic-join ,monad-var |
|||
(comprehend ,monad-var |
|||
(comprehend ,monad-var ,expr ,@(rest clauses)) |
|||
,(first clauses))))) |
|||
(t (destructuring-bind (var &rest container-exprs) (first clauses) |
|||
(cond |
|||
((and var (symbolp var)) |
|||
`(funcall (monadic-map ,monad-instance (lambda (,var) ,expr)) |
|||
,(first container-exprs))) |
|||
((and (consp var) (every #'symbolp var)) |
|||
`(multiple-value-call (monadic-map ,monad-instance |
|||
(lambda (,@var) ,expr)) |
|||
,@container-exprs)) |
|||
(t (error "COMPREHEND: bad variable specification: ~s" vars)))))))) |
|||
(defmacro define-monad (class-name |
|||
&key comprehension |
|||
(monad-param (gensym "MONAD-")) |
|||
bases slots initargs |
|||
((:map ((map-param) |
|||
&body map-body))) |
|||
((:join ((join-param |
|||
&optional |
|||
(j-rest-kw '&rest) |
|||
(j-rest (gensym "JOIN-REST-"))) |
|||
&body join-body))) |
|||
((:unit ((unit-param |
|||
&optional |
|||
(u-rest-kw '&rest) |
|||
(u-rest (gensym "UNIT-REST-"))) |
|||
&body unit-body)))) |
|||
`(progn |
|||
(defclass ,class-name ,bases ,slots) |
|||
(defmethod monadic-instance ((monad (eql ',class-name))) |
|||
(load-time-value (make-instance ',class-name ,@initargs))) |
|||
(defmethod monadic-map ((,monad-param ,class-name) ,map-param) |
|||
(declare (ignorable ,monad-param)) |
|||
,@map-body) |
|||
(defmethod monadic-join ((,monad-param ,class-name) |
|||
,join-param &rest ,j-rest) |
|||
(declare (ignorable ,monad-param ,j-rest)) |
|||
,@join-body) |
|||
(defmethod monadic-unit ((,monad-param ,class-name) |
|||
,unit-param &rest ,u-rest) |
|||
(declare (ignorable ,monad-param ,u-rest)) |
|||
,@unit-body) |
|||
,@(if comprehension |
|||
`((defmacro ,comprehension (expr &rest clauses) |
|||
`(comprehend (monadic-instance ',',class-name) |
|||
,expr ,@clauses)))))) |
|||
(defmethod monadic-map ((monad symbol) function) |
|||
(monadic-map (monadic-instance monad) function)) |
|||
(defmethod monadic-join ((monad symbol) container-of-containers &rest rest) |
|||
(apply #'monadic-join (monadic-instance monad) container-of-containers rest)) |
|||
(defmethod monadic-unit ((monad symbol) element &rest rest) |
|||
(apply #'monadic-unit (monadic-instance monad) element rest)) |
|||
(define-monad list-monad |
|||
:comprehension list-comp |
|||
:map ((function) (lambda (container) (mapcar function container))) |
|||
:join ((list-of-lists) (reduce #'append list-of-lists)) |
|||
:unit ((element) (list element))) |
|||
(define-monad identity-monad |
|||
:comprehension identity-comp |
|||
:map ((f) f) |
|||
:join ((x &rest rest) (apply #'values x rest)) |
|||
:unit ((x &rest rest) (apply #'values x rest))) |
|||
(define-monad state-xform-monad |
|||
:comprehension state-xform-comp |
|||
:map ((f) |
|||
(lambda (xformer) |
|||
(lambda (s) |
|||
(identity-comp (values (funcall f x) new-state) |
|||
((x new-state) (funcall xformer s)))))) |
|||
:join ((nested-xformer) |
|||
(lambda (s) |
|||
(identity-comp (values x new-state) |
|||
((embedded-xformer intermediate-state) |
|||
(funcall nested-xformer s)) |
|||
((x new-state) |
|||
(funcall embedded-xformer intermediate-state))))) |
|||
:unit ((x) (lambda (s) (values x s))))</lang> |
|||
=={{header|J}}== |
=={{header|J}}== |
Revision as of 22:27, 20 September 2011
![Task](http://static.miraheze.org/rosettacodewiki/thumb/b/ba/Rcode-button-task-crushed.png/64px-Rcode-button-task-crushed.png)
You are encouraged to solve this task according to the task description, using any language you may know.
Name and briefly demonstrate any support your language has for metaprogramming. Your demonstration may take the form of cross-references to other tasks on Rosetta Code. When possible, provide links to relevant documentation.
For the purposes of this task, "support for metaprogramming" means any way the user can effectively modify the language's syntax that's built into the language (like Lisp macros) or that's conventionally used with the language (like the C preprocessor). Such facilities need not be very powerful: even user-defined infix operators count. On the other hand, in general, neither operator overloading nor eval
count. The task author acknowledges that what qualifies as metaprogramming is largely a judgment call.
C
C preprocessor can be used to extend language to some extent.
It's possible to create static assertions
<lang c> // http://stackoverflow.com/questions/3385515/static-assert-in-c
- define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
// token pasting madness:
- define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L)
- define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L)
- define COMPILE_TIME_ASSERT(X) COMPILE_TIME_ASSERT2(X,__LINE__)
COMPILE_TIME_ASSERT(sizeof(long)==8); int main() {
COMPILE_TIME_ASSERT(sizeof(int)==4);
} </lang>
Another common usage is to create custom loops
<lang c> //requires C99
- define ITERATE_LIST(n, list) \
for(Node *n = (list)->head; n; n = n->next)
... ITERATE_LIST(n, list) {
printf("node value: %s\n", n->value);
} </lang>
For examples in real world, look FreeCiv, and OpenTTD macros(city_map_iterate for FreeCiv, FOR_ALL_ENGINES for OpenTTD).
Also, C does not support functions overloading, but becaus macro calls do not require type it's possible to emulate overloading to some extent
<lang c>
- define my_min(x, y) ((x) < (y) ? (x) : (y))
... printf("%f %d %ll\n", my_min(0.0f, 1.0f), my_min(1,2), my_min(1ll, 2ll)); </lang>
E
Forms of metaprogramming existant in E:
- Quasi-literals provide convenient notation for data structures and values for which there is not literal syntax, as discussed in String#E.
- E program fragments may be quoted, manipulated as an AST, and evaluated, similarly to Lisp; lexical environments are first-class objects (though static with respect to the evaluated code). Demonstrated in Runtime evaluation#E and Eval in environment#E.
- Control structures may be defined, as demonstrated in Extend your language#E.
Common Lisp
We can use Lisp macros, and other features, to add support to Lisp for monads, which come from functional languages. The following module of code provides a new macro form called COMPREHEND which works with monads. If we use the LIST monad, we get list comprehensions. For instance:
<lang lisp>
- The -> notation is not part of Lisp, it is used in examples indicate the output of a form.
(comprehend 'list-monad (cons x y) (x '(1 2 3)) (y '(A B C)))
-> ((1 . A) (1 . B) (1 . C) (2 . A) (2 . B) (2 . C) (3 . A) (3 . B) (3 . C))</lang>
As you can see, the comprehension processes all combinations of X and Y from both sets, and collects the application of (CONS X Y) to these elements.
In other words {(cons x y) | x ∈ { 1, 2 ,3 } ∧ y ∈ { A, B, C }}
Other monads are possible: idenitity, state transfomer, etc. Some of these are provided in the code below.
Furthermore, a form called DEFINE-MONAD is provided to define new kinds of monads. It is used to define the basic monads. DEFINE-MONAD also optionally generates a (trivial) short-hand comprehension macro for your monad type. So instead of (comprehend 'list ...) it is possible to write is also (list-comp ...).
Note how the state transformer monad uses the identity monad comprehension in its definition.
Also, a monad is a class, and there is a way in the DEFINE-MONAD syntax to declare what the base classes are (multiple inheritance) as well as any additional custom slots.
To see the original version of this code with lengthy comments, have a look in the Lisp Pastebin. http://paste.lisp.org/display/71196
<lang lisp>(defgeneric monadic-map (monad-class function))
(defgeneric monadic-join (monad-class container-of-containers &rest additional))
(defgeneric monadic-instance (monad-class-name))
(defmacro comprehend (monad-instance expr &rest clauses)
(let ((monad-var (gensym "CLASS-"))) (cond ((null clauses) `(multiple-value-call #'monadic-unit ,monad-instance ,expr)) ((rest clauses) `(let ((,monad-var ,monad-instance)) (multiple-value-call #'monadic-join ,monad-var (comprehend ,monad-var (comprehend ,monad-var ,expr ,@(rest clauses)) ,(first clauses))))) (t (destructuring-bind (var &rest container-exprs) (first clauses) (cond ((and var (symbolp var)) `(funcall (monadic-map ,monad-instance (lambda (,var) ,expr)) ,(first container-exprs))) ((and (consp var) (every #'symbolp var)) `(multiple-value-call (monadic-map ,monad-instance (lambda (,@var) ,expr)) ,@container-exprs)) (t (error "COMPREHEND: bad variable specification: ~s" vars))))))))
(defmacro define-monad (class-name
&key comprehension (monad-param (gensym "MONAD-")) bases slots initargs ((:map ((map-param) &body map-body))) ((:join ((join-param &optional (j-rest-kw '&rest) (j-rest (gensym "JOIN-REST-"))) &body join-body))) ((:unit ((unit-param &optional (u-rest-kw '&rest) (u-rest (gensym "UNIT-REST-"))) &body unit-body)))) `(progn (defclass ,class-name ,bases ,slots) (defmethod monadic-instance ((monad (eql ',class-name))) (load-time-value (make-instance ',class-name ,@initargs))) (defmethod monadic-map ((,monad-param ,class-name) ,map-param) (declare (ignorable ,monad-param)) ,@map-body) (defmethod monadic-join ((,monad-param ,class-name) ,join-param &rest ,j-rest) (declare (ignorable ,monad-param ,j-rest)) ,@join-body) (defmethod monadic-unit ((,monad-param ,class-name) ,unit-param &rest ,u-rest) (declare (ignorable ,monad-param ,u-rest)) ,@unit-body) ,@(if comprehension `((defmacro ,comprehension (expr &rest clauses) `(comprehend (monadic-instance ',',class-name) ,expr ,@clauses))))))
(defmethod monadic-map ((monad symbol) function)
(monadic-map (monadic-instance monad) function))
(defmethod monadic-join ((monad symbol) container-of-containers &rest rest)
(apply #'monadic-join (monadic-instance monad) container-of-containers rest))
(defmethod monadic-unit ((monad symbol) element &rest rest)
(apply #'monadic-unit (monadic-instance monad) element rest))
(define-monad list-monad
:comprehension list-comp :map ((function) (lambda (container) (mapcar function container))) :join ((list-of-lists) (reduce #'append list-of-lists)) :unit ((element) (list element)))
(define-monad identity-monad
:comprehension identity-comp :map ((f) f) :join ((x &rest rest) (apply #'values x rest)) :unit ((x &rest rest) (apply #'values x rest)))
(define-monad state-xform-monad
:comprehension state-xform-comp :map ((f) (lambda (xformer) (lambda (s) (identity-comp (values (funcall f x) new-state) ((x new-state) (funcall xformer s)))))) :join ((nested-xformer) (lambda (s) (identity-comp (values x new-state) ((embedded-xformer intermediate-state) (funcall nested-xformer s)) ((x new-state) (funcall embedded-xformer intermediate-state))))) :unit ((x) (lambda (s) (values x s))))</lang>
J
J names have one of four different grammatic types: noun, verb, adverb, conjunction. Nouns and verbs are nothing special from a metaprogramming point of view. However, adverbs and conjunctions are evaluated at "parse time" and can be used to introduce expression variants. (The result of an adverb, or of a conjunction may be either a noun, a verb, an adverb or a conjunction.)
Additionally, J script blocks (a block of text terminated by a right parenthesis on a single line) can and are used with differing interpreters (which may be built into the language or user written).
The J implementation of the Y combinator could be considered an example of metaprogramming.
J's explicit definitions might also be considered an example of metaprogramming, since explicit definitions have data dependent syntactic types.
Lua
Due to the way Lua's syntactic sugar is designed, metatables can make some Lua code look like a Domain-Specific Language, despite technically being (mostly) just specialized operator overloading.
For example: <lang lua> class "foo" : inherits "bar" {
} </lang>
is perfectly valid syntax. (Lua does not having a built in concept of classes or inheritance.)
Nemerle
Nemerle provides support for macros, which range from defining new infix operators (in fact many 'built-in' operators are macros) to new keywords or control structures.
See here, here, and here on the Nemerle wiki for more information.
PARI/GP
The primary means of metaprogramming for GP is to extend it with PARI. As an example, defining an infix @
operator could work like
In src/functions/symbolic_operators/min0:
Function: _@_ Help: x@y: compute the lesser of x and y, or 0, whichever is larger. Section: symbolic_operators C-Name: gmin0 Prototype: GG Description: (small, small):small smin0ss($1, $2) (mp, mp):mp gmin0($1, $2) (gen, gen):gen gmin0($1, $2)
In (e.g.) basemath/somefile.c: <lang C>long smin0ss(long a, long b) {
long c = a < b ? a : b; return c > 0 ? c : 0;
}
GEN
gmin0(GEN a, GEN b)
{
GEN c = gcmp(a, b) < 1 ? a : b; /* copy pointer */ return signe(c) > 0 ? gcopy(c) : gen_0;
}</lang>
Perl
You can textually transform code with a source filter, a module that when use
d modifies the following lines of source. Filter::Util::Call provides a general means of writing source filters. Filter::Simple is an interface to Filter::Util::Call
that lets you elide a lot of boilerplate code. More important, Filter::Simple
can hide the contents of quoting constructs from your filter, obviating the biggest dangers of textual metaprogramming. For example, given the following module:
<lang perl>package UnicodeEllipsis;
use Filter::Simple;
FILTER_ONLY code => sub { s/…/../g };</lang>
this program:
<lang perl>use UnicodeEllipsis;
print join(' … ', 1 … 5), "\n";</lang>
prints:
1 … 2 … 3 … 4 … 5
Devel::Declare lets you define a new keyword by setting up a hook to be run whenever the parser encounters a bareword of your choice. Devel::Declare is powerful, but it has a reputation for being difficult to understand. See this blog post for a relatively simple usage example.
Perl 6
Perl 6 makes it very easy to do metaprogramming. It is a basic goal of the language.
It is trivial to add a new operator. Most Perl 6 operators are written as normal multiple-dispatch functions in the setting (known as a "prelude" in other languages, but in Perl 6 the setting is a lexical scope notionally surrounding your compilation unit).
There is no a built in factorial operator Perl 6. It was purposely left out to use as a demonstration of how easy it is to add it. :-)
<lang perl6>sub postfix:<!> { [*] 1..$^n } say 5!; # prints 120</lang>
You may augment a base class with a new method, as long as you declare that you are going to cheat.
Here we add a new method to do natural sorting to the base class Any. (List and Array are both subclasses of Any)
<lang perl6>use MONKEY_TYPING; # Needed to do runtime augmentation of a base class.
augment class Any {
method nsort { self.list.sort: {$^a.lc.subst(/(\d+)/,->$/{0~$0.chars.chr~$0},:g)~"\x0"~$^a} }
};
say ~<a201st a32nd a3rd a144th a17th a2 a95>.nsort; say ~<a201st a32nd a3rd a144th a17th a2 a95>.sort;</lang>
Prints
a2 a3rd a17th a32nd a95 a144th a201st a144th a17th a2 a201st a32nd a3rd a95
Perl 6 specs hygienic macros, but no implementation yet supports these. The pugs implementation supports text macros: <lang perl6>macro addem($a,$b) { "($a + $b)" } say addem(3,4); # 7</lang> Grammar mixins work in Perl 6 because grammar rules are just methods in grammar classes, and Perl 6 automatically writes a JIT lexer for you whenever you derive a new language. This functionality already works internally in the standard parser—what is not yet implemented is the augment slang hook to allow user code to do this mixin. Perl 6 itself is already parsed using such grammar mixins to provide extensible quoting and regex syntax. For example, every time you pick your own quote characters, you're actually deriving a new Perl 6 dialect that supports those start and stop characters. Likewise any switches to impose single or double-quote semantics, or heredoc semantics, is merely a grammar mixin on the basic Q language. <lang perl6>say "Foo = $foo\n"; # normal double quotes say Q:qq 【Foo = $foo\n】; # a more explicit derivation, new quotes</lang>
PicoLisp
As in any Lisp, metaprogramming is an essential aspect of PicoLisp. In most cases normal functions are used to extend the language (see Extend your language#PicoLisp), read-macros operate on the source level, and also runtime macros are used occasionally.
PostScript
PostScript allows the reification of stack, scoping (dynamic scoping is default, but lexical scoping can be implemented using immediate loading), bindings using dictionaries, and even control flow. Here is an example of implementation of if statement
<lang postscript>
/ift { 4 dict begin
[/.if /.then] let* count array astore /.stack exch def /_restore {clear .stack aload pop}. .stack aload pop .if { _restore .then } { _restore } ifelse
end}. </lang> The standard if expression in PostScript does not take a predicate. Instead it acts on the boolean value on top of the stack. This newly created word allows us to do <lang postscript> >| 2 {1 gt} {=} ift 2 </lang> Instead of
<lang postscript> >| 2 dup 1 gt {=} ift 2 </lang>
Note that even the let expression was implemented using meta programming <lang postscript> /let* {reverse {exch def} forall}. </lang>
Python
Metaprogramming is frowned on in Python and considered un-pythonic. The only widely known example of metaprogramming in Python was an implementation of a goto (and comefrom) keyword done as an April-fools joke.
REXX
REXX doesn't allow for the changing or overriding of syntax per se, but any of built-in functions can be overided by just specifying your own.
REXX allows the programmer to set the precision of the numbers it uses in calculations and expressions, as well as the FUZZ settings, which effects how numbers are compared (for equalness, greater or equal to, less or equal to, etc).
Ruby
An rudimentary example of metaprogramming is presented in this simple identification system template: <lang ruby> class IDVictim
#_Create elements of this man, woman, or child's identification. attr_accessor :name, :birthday, :gender, :hometown #_Allows you to put in a space for anything which is not covered by the #_preexisting elements. def self.new_element(element) if "#{element.class}" == "Symbol" attr_accessor element elsif "#{element.class}" == "String" attr_accessor element else raise ArgumentError, "Cannot mold an element out of a/an #{element.class}. \ Must be a Symbol or a String." end end
end </lang>
The "self.new_element" class method allows one to (later) specify a new attribute to be added to the defaults of "name", "birthday", "gender", and "hometown".
SNOBOL4
There are several features of SNOBOL4 which could be considered meta-programming. The first of these is the ability to define synonyms for existing operators or functions, a feature which can help in creating DSLs of sorts in SNOBOL4 programs. For example the following code will alias the built-in function IDENT
to SAME
and the unary operator *
to $
:
<lang snobol4>
OPSYN('SAME','IDENT') OPSYN('$','*',1)
</lang>
This is a simplistic use of OPSYN
, however. More interesting is the aliasing of a function to an operator:
<lang snobol4>
OPSYN('F','*',1)
</lang>
In this setup, calling F(X)
is the same as using the sequence *X
which, in more complicated expressions, could result in better readability.
Other metaprogramming features supported would include the use of unevaluated expressions. If, in code, E
is an expression it has a value as soon as it is defined and/or assigned to. *E
, on the other hand, has a value only when it is evaluated either in the context of a pattern or in the context of an EVAL
. The following example shows the motivation for unevaluated expressions in pattern matching contexts:
<lang snobol4>
&ANCHOR = 0 ; &TRIM = 1 WORD = BREAK(' .,') . W SPAN(' .,') STRING1 = INPUT :F(ERROR) STRING2 = INPUT :F(ERROR)
LOOP STRING1 WORD = :F(OUTPUT)
STRING2 ' ' W ANY(' .,') :F(LOOP) LIST = LIST W ', ' :(LOOP)
OUTPUT OUTPUT = LIST END </lang>
In this code, two strings are input and a list of words appearing in both strings is generated. The problem with this code is that the pattern structure ' ' W ANY(' .,')
is built on each iteration. Since pattern building is expensive, putting it in a loop like this is bad form. It cannot be moved outside of the loop, however, since the value of W changes for each iteration. The solution to this is to defer the evaluation of the variable W
until it is needed while keeping the rest of the pattern intact:
<lang snobol4>
&ANCHOR = 0 ; &TRIM = 1 WORD = BREAK(' .,') . W SPAN(' .,') FINDW = ' ' *W ANY(' .,') STRING1 = INPUT :F(ERROR) STRING2 = INPUT :F(ERROR)
LOOP STRING1 WORD = :F(OUTPUT)
STRING2 FINDW :F(LOOP) LIST = LIST W ', ' :(LOOP)
OUTPUT OUTPUT = LIST
END
</lang>
In this code, the pattern is constructed only once in the line FINDW = ' ' *W ANY(' .,')
. The value of the variable W
, however, is only provided when FINDW is used in a pattern match. In this case it is given its value from the line before when STRING1
is matched against the pattern WORD
. In this way the expense of building the pattern is paid only once, but the flexibility of matching a sequence of values is retained.
The final example of metaprogramming that's available lies in the idiosyncratic way that user-defined functions work in SNOBOL4. The fact that the DEFINE
function can be recalled at any point to redefine any function is a powerful feature that can lead to very efficient code. (It can also lead to very unreadable code, of course, if not properly used.)
Consider this hand-waving example for the motivation: <lang snobol4>
- This example provides a bizarrely-expensive addition operation.
- It assumes the existence of an expensive procedure—say a database
- lookup—to extract the value to be added. This version uses the
- typical initialize-on-definition approach to implementation.
DEFINE('XADD(X)','XADD') ADDVALUE = CALL_SOME_EXPENSIVE_OPERATION() :(XADD.END)
XADD XADD = X + ADDVALUE :(RETURN) XADD.END </lang>
In normal operation the interpreter will execute the DEFINE
function and then execute the ADDVALUE = ...
line, branching *past* the actual body of the function to the label XADD.END
. If, however, there are many such functions, and especially if there's the possibility that these functions are never actually called, this could render program startup very slow. For purposes of amortizing initialization time, or for purposes of saving unnecessary initialization, the following code is better:
<lang snobol4>
DEFINE('XADD(X)','XADD.INIT') :(XADD.END)
XADD.INIT ADDVALUE = CALL_SOME_EXPENSIVE_OPERATION()
DEFINE('XADD(X)','XADD')
XADD XADD = X + ADDVALUE :(RETURN) XADD.END </lang>
The code now defines the XADD
function and immediately, without doing initialization, jumps to the XADD.END
label, bypassing both initialization and the function body. The trick here is that it defines the entry point of the function to be the XADD.INIT
label. Now the first time XADD
is called, control is transferred to XADD.INIT
, the expensive initialization is performed, then the XADD
function is *redefined* to point to the XADD
label as the entry point. From this point onward all calls to XADD
only perform the calculation, not the expensive initialization while the expensive initialization isn't paid at all unless the function is used at least once.
There are, of course, many other uses for function redefinition in this style which are suitable for metaprogramming efforts. Indeed such features are used prominently in the debugging subsystems of SNOBOL4 implementations.
Tcl
Metaprogramming is considered to be normal in Tcl; the whole language was designed to support new operations that work with a similar level of integration to existing commands (and indeed, the standard commands are not syntactically special in any way), and the upvar
and uplevel
commands are specifically for this sort of use. Moreover, there are no language keywords that need to be worked around; words/tokens can be used to mean anything necessary. For example:
<lang tcl>proc loopVar {var from lower to upper script} {
if {$from ne "from" || $to ne "to"} {error "syntax error"} upvar 1 $var v if {$lower <= $upper} { for {set v $lower} {$v <= $upper} {incr v} { uplevel 1 $script } } else { # $upper and $lower really the other way round here for {set v $lower} {$v >= $upper} {incr v -1} { uplevel 1 $script } }
}</lang>
The above creates a new loopVar
command that might be used like this:
<lang tcl>loopVar x from 1 to 4 {
loopVar y from $x to 6 { puts "pair is ($x,$y)" if {$y >= 4} break }
}</lang> Which prints this:
pair is (1,1) pair is (1,2) pair is (1,3) pair is (1,4) pair is (2,2) pair is (2,3) pair is (2,4) pair is (3,3) pair is (3,4) pair is (4,4)
As you can see, the new looping command is wholly integrated with the rest of the Tcl language.
Code generation is also possible. The easiest approach is to use the list
command to generate substitution-free command, leaving all substitutions to places that are written by the programmer directly.
Finally, the total lack of keywords is exemplified by this classic fragment of Tcl:
<lang tcl>set set set</lang>
In this, the first set
is a command (that updates a variable), the second is the name of a variable in the current namespace, and the third is a string that will be placed in a variable.