User Tools

Site Tools


at:tutorial:metaprogramming

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
at:tutorial:metaprogramming [2007/05/04 02:45]
stimberm
at:tutorial:metaprogramming [2009/11/21 07:44] (current)
tvcutsem
Line 1: Line 1:
-<note>This tutorial is under heavy construction!</note> 
- 
 ===== Metaprogramming ===== ===== Metaprogramming =====
  
-==== AT Zero - AmbientTalk without syntactic sugar ====+==== AmbientTalk without syntactic sugar ====
  
 In AmbientTalk, everything is an object, also native values such as numbers, booleans, strings and tables. Hence, the only way to interact with these values is by sending messages to them. However, there are some constructs in AmbientTalk that do not seem to send a message at all. Examples are table access/assignment, control structures and arithmic operations: In AmbientTalk, everything is an object, also native values such as numbers, booleans, strings and tables. Hence, the only way to interact with these values is by sending messages to them. However, there are some constructs in AmbientTalk that do not seem to send a message at all. Examples are table access/assignment, control structures and arithmic operations:
Line 14: Line 12:
 >>7 >>7
 </code> </code>
-However, these constructs are all **syntactic sugar**. Behind the scenes, they all perform message sends. The following code shows the equivalents of the previous code, but with the actual message sends:+However, these constructs are all **syntactic sugar** that enable a more natural syntax for performing these operations. Behind the scenes, they all perform message sends. The following code shows the equivalents of the previous code, but with the actual message sends:
 <code> <code>
 >[5, 6, 7].at(2) >[5, 6, 7].at(2)
Line 24: Line 22:
 </code> </code>
  
-Another special AmbientTalk construct is the literal closure. A literal closure is just an object with a special //apply// method that will execute the body of the literal closure.+In the first example above the message //at// is sent to the table **[5,6,7]**.  The second example sends the keyword message //ifTrue:ifFalse:// to the boolean value returned after evaluating the expression **(1 == 2)**. Notice the usage of the curly braces.  These wrap the expressions in a literal closure object A literal closure is just an object with a special //apply// method that will execute the body of the literal closure.  The usage of these closures ensures lazy evaluation of the actual arguments of //ifTrue:ifFalse:// similar to the use of blocks in Smalltalk or Self.  Finally, in the third example, mathematical expressions are converted to method invocations on numbers.
  
 ==== Quasiquoting and splicing ==== ==== Quasiquoting and splicing ====
 +Quasiquoting and splicing are an advanced and powerful metaprogramming techniques that control the evaluation process and allow one to manipulate the evaluation process of the abstract syntax tree.
  
-=== Qouting ===+=== Quoting ===
  
 Any valid AmbientTalk expression can be quoted. This prevents the expression from being evaluated. Instead, it is returned literally. Any valid AmbientTalk expression can be quoted. This prevents the expression from being evaluated. Instead, it is returned literally.
Line 61: Line 60:
 </code> </code>
  
-Note that in ''`foo.text'', the quoted expression is ''foo'' and //not// ''foo.text''. The selection is performed on the result of the quotation, in this case a symbol.+Note that in ''`foo.text'', the quoted expression is ''foo'' and //not// ''foo.text''. The selection of the field //text// is performed on the result of the quotation, in this case a symbol.
  
 == Expressions ==  == Expressions == 
  
-To quote a complete expression, it has to be wrapped in parantheses:+To quote a complete expression, it has to be wrapped in parentheses:
  
 <code> <code>
Line 80: Line 79:
 == Statements == == Statements ==
  
-Statements (definitions, assignments, ...) can also be quoted, but only inside a quoted statement list. Trying to quote a statement in the same way as an expression will cause a parse error.+Statements (definitions, assignments, ...) can also be quoted, but only inside a quoted statement list. Trying to quote a statement in the same way as an expression will cause a parse error.  Instead it is necessary to wrap the statements in a closure using ``{'' and ``}''.
  
 <code> <code>
Line 103: Line 102:
 With this construct, all the elements of the literal table are evaluated. By quoting a literal table, all the elements are quoted instead of evaluated: With this construct, all the elements of the literal table are evaluated. By quoting a literal table, all the elements are quoted instead of evaluated:
 <code> <code>
->def tab := `[ 1+2, 3+4, 5+6 ]+>def anotherTab := `[ 1+2, 3+4, 5+6 ]
 >>[1.+(2), 3.+(4), 5.+(6)] >>[1.+(2), 3.+(4), 5.+(6)]
 </code> </code>
Line 129: Line 128:
 >[ 7, 8, 9, @upTo(4) ] >[ 7, 8, 9, @upTo(4) ]
 >>[7, 8, 9, 1, 2, 3, 4] >>[7, 8, 9, 1, 2, 3, 4]
 +>[ 7, 8, 9, upTo(4) ]
 +>>[7, 8, 9, [1, 2, 3, 4]]
 </code> </code>
 +In the example above the elements of the table returned by invoking **upTo(4)** are added in place to the table in which the expression was spliced.  Evaluating the same expression without the splice operator adds the table rather than the elements of the table.  Hence, the use of the splice operator removes a level of nesting and adds the elements //in place// to the table.
  
 Splicing can also be used in combination with quoting and unquoting. AmbientTalk provides the //unquote-splice// operator ''#@'' that can be used to splice the value of an unquotation into a quoted expression. Splicing can also be used in combination with quoting and unquoting. AmbientTalk provides the //unquote-splice// operator ''#@'' that can be used to splice the value of an unquotation into a quoted expression.
Line 193: Line 195:
 >read: "1+2" >read: "1+2"
 >>1.+(2) >>1.+(2)
->eval: `(1+2) in: self+>def result := eval: `(1+2) in: self
 >>3 >>3
->print: self +>print: result 
->>"<object:8322247>"+>>"3"
 </code> </code>
  
Line 203: Line 205:
 <code> <code>
 >def o := object: { def x := 4 } >def o := object: { def x := 4 }
->><object:3281169>+>><obj:{super,super:=,x,x:=}>
 >eval: `x in: o >eval: `x in: o
 >>4 >>4
 </code> </code>
 +
 +==== Multi-stage (Generative) Programming ====
 +
 +Here's a small example of "compile-time" metaprogramming or "multi-stage programming" inspired by the same example from the E language on [[http://www.erights.org/elang/examples/multi-stage.html|Multi-stage programming in E]]:
 +
 +Below is a regular power function. Given two numbers 'x' and 'n', it returns 'x^n':
 +
 +<code javascript>
 +def pow(x, n) {
 +  if: (n == 0) then: {
 +    1
 +  } else: {
 +    x * pow(x,n-1);
 +  }
 +};
 +</code>
 +
 +Let's see whether it works:
 +<code javascript>
 +system.println(pow(2,5)); // prints 32
 +</code>
 +
 +Now, consider the following 'expandPow' function that is very similar to the above function, but which, instead of //calculating the value// of the power function will //build an expression// that, when evaluated, yields the value of the power function. Note that this function is parameterized with the name of the variable that is used to calculate the power value:
 +
 +<code javascript>
 +def expandPow(var, n) {
 +  if: (n == 0) then: {
 +    `1 // `exp returns an abstract syntax tree for exp
 +  } else: {
 +    // within a quoted expression, #(exp) evaluates exp,
 +    // expects it to return an AST, and embeds that AST
 +    // in the quoted expression
 +    `(#var * #(expandPow(var, n-1)));
 +  }
 +};
 +
 +// this prints the expression 'y * y * y * 1'
 +system.println("expandPow(`y,3) = " + expandPow(`y,3));
 +</code>
 +
 +To be able to use the expression generated by the above function, let's define a small helper function that will embed this expression in a first-class function: 
 +
 +<code javascript>
 +def powMaker(n) {
 +  def ast := expandPow(`x, n);
 +  // the built-in function 'eval:in:' takes an expression and an object and
 +  // evaluates the expression in the scope of the given object
 +  // return a function that has the expanded expression as its body:
 +  eval: `({|x| #ast}) in: self;
 +};
 +</code>
 +
 +Now we can generate power functions that are fixed in their second argument, but that are more efficient to execute:
 +
 +<code javascript>
 +// pow5 is now bound to a function { |x| x*x*x*x*x*1 }
 +def pow5 := powMaker(5);
 +
 +system.println(pow5(2)); // yields 32, as expected
 +</code>
 +
 +You can measure the performance difference by timing the evaluation of both functions:
 +
 +<code javascript>
 +import /.at.support.timer
 +system.println("pow(2,5) takes " + (time: { pow(2,5) }) +"ms");
 +system.println("pow5(2) takes " + (time: { pow5(2) }) +"ms");
 +</code>
 +
 +The quoting and unquoting mechanism of AmbientTalk makes it really easy to "partially evaluate" a function in one of its arguments.
 +
 +The above example is available in the AmbientTalk library, under '/at/demo/metaprogramming.at'.
at/tutorial/metaprogramming.1178239541.txt.gz · Last modified: 2007/07/27 07:21 (external edit)