Metaprogramming

It is recommended that the reader be familiar with Heron code blocks before continuing with this document

Metaprogramming, in the context of this documentation at least, refers to the ability to perform computations and to generate code at compile time. Heron metaprogramming is done entirely through the use of templates.

Heron metaprogramming is a very powerful sub-language of Heron that is functional in nature and turing-complete. Heron metaprogramming is inspired by the Boost template metaprogramming library. Like C++, Heron metaprogramming works through the type-system, but unlike C++ the type-system was designed deliberately with metaprogramming in mind.

In Heron metaprogramming is explicitly and deliberately separate from run-time computation. This allows Heron to have more expressiveness at compile-time by reducing ambiguity from run-time code. In several languages it is left up to the compiler to decide, typically in an implementation specific manner, whether a function can be evaluated at run-time or compile-time. Determining whether a function is evaluatabe at run-time or compile-time can be difficult for a programmer. In Heron all functions are evaluated only at run-time, whereas all meta-functions are evaluated at compile time.

The conundrum can be made apparrent by considering that virtually every behaviour of a Heron program is defined within libraries, which can depend entirely on conditions not known until program execution. The challenge then would be that a Heron program would have to be run while it is being compiled, making for extremely complex conditions.

Meta-values and the Value-of Operator

A meta-value is a type, and has no storage allocation. A meta-value literal looks like a run-time literal value, e.g. 42, 3.141, "hello", etc., but can be recognized by its context. For instance, since only types can be passed to templates, within the code snippet: SizedArray<Int, 99>, we can recognized 99 as being a meta-value. Also when using define to create an alias to a value, we know that it is a meta-value being aliased.

Sometimes it is desirable to convert from a meta-value to run-time value. This is done explicitly through the "value-of" operator ( $ ). This operators takes a compile-time expression ( i.e. a meta-value ), and returns an appropriately typed run-time literal value. For instance:

  types {
    define pi : 3.141; // by context, we recognize this as a meta-value.
  }
  functions {
    _main() {
      print_float(pi); // error: pi is not a valid expression, it is a meta-value
      print_float($pi); // fine: outputs 3.14
    }
  }
  

Meta-primitives

The meta-primitives are separate classifications of meta-types. You could think of them as meta-meta-types. The meta-primitives are: meta_int, meta_float, meta_char, meta_string and meta_bool. Note: meta-variables are commonly used in Heron to replace constant variables in other languages, or macros in C, but must be prefixed with the meta_value operator ($) when used in a run-time context.

Meta-functions

A meta-function is a type-alias, that may or may not be parameterized. Typically if a meta-function has no parameters it is called a meta-variable. Here is an example of the definition and invocation of a meta-function.
  types {
    define f_double<x> : f_mult<x, 2.0>;
    define two_pi : f_double<pi>;
  }
  

Meta-variables

Meta-variables are simply meta-functions with no parameters. For example:
  types {
    define pi : 3.14;
  }
  

Meta-expansion operator and meta-code-blocks

One of the more exciting features of Heron is the ability to use and define code-blocks as meta-values. A code-block is a set of statements enclosed inside of a matching curly brace pair ({}). A meta-code-block is parsed for correct syntax but is not compiled unless it is expanded using the meta-expansion operator, hash (#). A meta-expansion operator is similar to the meta-value operator but it only applies to meta-code-blocks. An expanded meta-code-block statement, is compiled in place using the same local scope as if it had been written in place. ( Note: there is one difference though, an expanded meta-code-block does not have visibility of template parameters of the scope where it is expanded. ) A simple example of a meta-code-block expansion is:
  types {
    define print_fubar : { print_string("fubar\n") }
  }
  functions {
    _main() {
      #print_fubar;
    }
  }
  
A slightly more interesting example which exploits the locality of an expansion is as follows:
  types {
    define print_x : { print_int(x) }
  }
  functions {
    _main() {
      #print_x; // error: x is not defined
      _int x = 3;
      #print_x; // outputs 3 to the standard out
    }
  }
  
Finally a simple, but useful application of meta-function involving code-blocks is a variation of the classic Hello World program.
  program MetaHelloWorld;
  uses {
    std.system;
  }
  types {
    define concat_blocks<meta_code T1, meta_code T2> :
      { #T1 #T2 };
  }
  functions {
    _main() {
      #concat_blocks
      <
        {print_string("hello ");},
        {print_string("world\n");}
      >
    }
  }
  end
  

meta_if

The meta_if compile-time operator takes three type arguments, the first argument must be a meta_bool meta-value, but the second and third arguments can be any type. The meta_if operator returns the second argument if the first argument is equal to true, but returns the third argument if the first argument is equal to false. Unlike a meta-function, meta_if only evaluates either the second or third arguments, not both. The following example uses meta_if within a recursive loop to repeat a code block multiple times.
  program HelloWorldX;
  uses {
    std.system;
  }
  types {
    define concat_blocks<meta_code T1, meta_code T2> :
      { #T1 #T2 };

    define repeat_block<meta_code T, meta_int i> :
      meta_if
      <
        i_gt<i, 0>,
        concat_blocks<T, repeat_block<T, i_dec<i>>>,
        { }
      >;
  }
  functions {
    _main() {
      #repeat_block<{ print_string("hello world\n"); }, 10>
    }
  }
  end
  

size_of

The size_of meta-operator takes a single type and returns the size in bytes of the storage allocation neccessary for a variable of that given type. If the argument is a meta-type then the result is 0. For example:
    _main() {
      print_int($size_of<_int>); // outputs 4
      print_int($size_of<_char>); // outputs 1
      print_int($size_of<pi>); // outputs 0
    }
  

type_eq

The type_eq meta-operator returns the meta_bool meta-value true if the two arguments are of the same type otherwise it returns false. Type-aliases are the same as the type they are aliasing. Meta-types are always different.

More meta-operations

Heron supports a rich set of meta-operations:

namesignaturedescription
i_addmeta_int, meta_int ::= meta_intadd
i_submeta_int, meta_int ::= meta_intsubtact
i_divmeta_int, meta_int ::= meta_intdivide
i_multmeta_int, meta_int ::= meta_intmultiply
i_modmeta_int, meta_int ::= meta_intmodulo
i_negmeta_int ::= meta_intnegative
f_sinmeta_float ::= meta_floatsine
f_cosmeta_float ::= meta_floatcosine
f_tanmeta_float ::= meta_floattangent
f_addmeta_float, meta_float ::= meta_floatadd
f_submeta_float, meta_float ::= meta_floatsubtract
f_divmeta_float, meta_float ::= meta_floatdivide
f_multmeta_float, meta_float ::= meta_floatmultiply
f_negmeta_float, meta_float ::= meta_floatnegative
s_catmeta_string, meta_stringconcat strings
s_openmeta_string ::= meta_stringget contents of file
s_lenmeta_string ::= meta_intlength of string
s_atmeta_string, meta_int ::= meta_char character at index
b_andmeta_bool, meta_bool ::= meta_bool boolean at
b_ormeta_bool, meta_bool ::= meta_boolboolean or
b_notmeta_bool ::= meta_boolboolean not
f_gtmeta_float ::= meta_float greater than
f_ltmeta_float, meta_float ::= meta_bool less than
i_gtmeta_int, meta_int ::= meta_boolgreater than
i_ltmeta_int, meta_int ::= meta_boolless than
i_gteqmeta_int, meta_int ::= meta_boolgreater than or equal
i_lteqmeta_int, meta_int ::= meta_boolless than or equal
i_eqmeta_int, meta_int ::= meta_boolequal
i_neqmeta_int, meta_int ::= meta_boolnot equal
i_eq_zerometa_int ::= meta_boolequal to zero
i_neq_zerometa_int, meta_int ::= meta_boolnot equal to zero
i_incmeta_int ::= meta_intplus one
i_decmeta_int ::= meta_intminus one

Future Directions

It is expected that later versions of Heron will introduce meta-abstract-data-types. This is a straightfoward and logical extension of Heron meta-programming.

It is conceivable that a future version of Heron will allow metaprogramming that has the precisely the same syntax as the rest of the language.