Keywords

These keywords were added by machine and not by the authors. This process is experimental and the keywords may be updated as the learning algorithm improves.

1 Introduction

There are two kinds of meta-programming: homogeneous and heterogeneous. Here, we focus on homogeneous meta-programming only.

The term ‘homogeneous meta-programming’ was coined by Sheard [She01] to characterize meta-programming based on the identity of the meta- and object (or target; further, we use the term ‘target language’ so as not to confuse a reader, since object language is just the object of manipulation and has nothing to do with object-oriented programming languages) language. A homogeneous meta-program, written in a language L, is a meta-program that constructs or manipulates other L programs [Pas04]. Homogeneous meta-programming first originated in formal meta-programming systems and languages such as Scheme, Haskell and ML, which were, in essence, functional programming languages. Later it was introduced to object-oriented programming languages such as C++, Java and C#, where main abstractions are data types and classes. Therefore, the division of homogeneous meta-programming into functional and structural homogeneous meta-programming has appeared.

The extent and scope of homogeneous meta-programming capabilities is closely related to the particular capabilities and built-in abstractions of a target language. For example, in functional programming languages, abstract syntax of target programs can be represented using algebraic data types, whereas other higher-level programming languages have different structured data facilities such as object hierarchies in Java. Some programming languages provide only capabilities either for structural meta-programming, such as VHDL, or for functional meta-programming only, such as Haskell, whereas other languages have capabilities for both kinds of meta-programming. Further in this chapter, we provide examples in Java, which since 2004 has abstractions and facilities for generic programming and can be used to program both structural and functional meta-programs.

We restrict our analysis in this chapter to two different languages. One is the general-purpose programming language Java. Another is the domain-specific language VHDL (VLSIC – Very Large Scale Integrated Circuit Hardware Description Language). Motivation for this case study is as follows. VHDL supports only structural homogeneous meta-programming in two different ways (e.g. generalization at the statement level and scripting or composition at the component level), whereas Java has capabilities for both functional and structural meta-programming. We also present a case study in VHDL.

Next, we discuss the language-independent aspects of homogeneous meta-programming.

2 Language-Independent Aspects of Homogeneous Meta-Programming

The aim of this chapter is to analyse homogeneous meta-programming not in ad hoc manner but as a systematic and well-established approach. Such a view requires a great deal of analysis based on adaptation and interpretation of principles formulated in Chap. 3. On the other hand, a systematic approach also depends on language capabilities. In that context, we can speak about specificity of homogeneous meta-programming using some specific language. Thus, we restrict our analysis to two different languages (Java and VHDL).

To achieve generalization, various manipulations are to be applied to the program code according to the prescribed requirements for generalization and capabilities of a given language. As a result of the process, a more general program, called a meta-program, is devised. For better understanding of how this process takes place, one should focuses on two underlying interrelated principles: separation of concerns and integration of concerns, when they are implemented in the homogeneous meta-programming paradigm.

In general, there are two forms of concerns separations, namely, implicit and explicit separation (see Chap. 3). Two concerns are said to be explicitly separated if they are orthogonal and their representation is obviously expressed; otherwise, they may be treated or not as implicitly separated. The problem with implicit separation of concerns is that concerns often are non-orthogonal, that is, dependable. To describe non-orthogonal concepts, a more appropriate term, namely, cross-cutting of concerns, is usually used. We return to this kind of separation of concerns later, when we consider other kinds of meta-programming, such as aspect-oriented programming.

At the core of homogeneous meta-programming systems is the use of both implicit and explicit separation of concerns. What concerns are or should be separated in this programming paradigm? The first concern is the abstraction level. In a typical meta-program, there are two abstraction levels: meta-level and object-level. Meta-level is often referred as a higher-level because, at this level, we describe generalizations. Object-level is also referred to as a lower-level because, at this level, we describe specific functionality without intention for generalization.

Abstraction levels are seen through the language concepts. As there is the only one language, we can speak about subsets of a given language aiming to identify and separate the higher-level subset from the lower-level subsets. Homogeneous meta-programming may exploit either explicit or implicit separation of concerns in terms of abstraction levels. This depends upon capabilities of a language.

If, in some language, there are constructs that were introduced by the language designers specifically for generalization purposes (e.g. templates in C++), we can say that the language has the higher-level subset, which is identified explicitly (the rest constructs form a lower-level subset). One should take into account that this is a weak form of explicit separation of concerns because it is impossible to use high-level constructs without the support of lower-level constructs. The implicit separation of concerns (in this context, language subsets) appears in the case when the user of the language identifies higher-level and lower-level subsets in order to implement generalization as it takes place in the case of using VHDL.

Figure 4.1 explains a framework of homogeneous meta-programming within a single programming environment, where lower-level and higher-level subsets of a given language are identified as a part of concerns separation. The lower-level (LL) subset serves for describing lower-level manipulations, that is, operations (algorithm) with data structures. The process identifies some fragments of a program. The higher-level (HL) subset serves for describing higher-level manipulations, while arguments to perform the manipulations are fragments of a program. The framework also defines integration of concerns (denoted abstractly as \( \oplus \) in Fig. 4.1) via the linking of higher and lower levels together in the same specification.

Fig. 4.1
figure 1

Framework of homogeneous meta-programming

The simplest case of the linking/integrating mechanism is known as parameterization which is usually introduced through the language, where a program template that represents a higher-level subset describes manipulations with the lower-level representation in order to express generalizations. To understand how the mechanism of generalization through parameterization works in practice, two key questions must be answered: (1) what are higher-level structures that enable manipulations to support homogeneous meta-programming and (2) what are arguments of operations that perform manipulations?

In homogeneous meta-programming, there are some restrictions to perform manipulations. Typically, arguments for manipulation are complete syntactic structures, such as statements and procedures. What are higher-level structures and how they are exploited depend upon the capabilities of a given language. This topic is considered in the next subsections.

Further, we proceed with introducing taxonomy of homogeneous meta-programming techniques and basic terminology in the domain.

3 Terminology, Taxonomy and Formal Description of Homogeneous Meta-Programming Domain

3.1 Functional Homogeneous Meta-Programming

Before starting the formal description of the functional meta-programming domain, first, we introduce the main concepts. In functional programming, the main concept is a function. In mathematics, a function is defined as a mathematical relation such that each element of a given set (the domain of the function) is associated with an element of another set (the range of the function). In programming languages, sets are defined and described through the concept of types. So, the function can be defined as a portion of program code that performs a calculation over its input variable values of a specific type(s) and returns output value of a specific type that is the same or different than input variable type(s).

Using this analogy to mathematics and functional programming, we can define the main concept of functional meta-programming domain as a meta-function. A meta-function is a portion of meta-program code that performs a calculation over its inputs (function(s) of a specific or generic type) and returns a function of a specific or generic type. Another definition is given by Iglesias [Igl05]: “Meta-functions are (polytypic) functions that produce code from actual type arguments.” Note that code is produced from types rather than type values. In pure object-oriented languages, functions are encapsulated in types (abstract classes/interfaces or classes) so there is no contradiction between these two definitions. Another definition focuses on the application time of meta-functions: ‘A meta-function is a class or a class template that represents a function invocable at compile-time’ [AG04].

Note that here we distinguish between meta-functions and generic functions. A generic (or polymorphic) function is a generalization of a function over input and output types: a generic function accepts and/or produces values of a generic type, which are substituted for specific types at compile time. As such, it can be vied as a semi-meta-function. On the other hand, meta-functions operate on the function domain rather than type domain: meta-functions accept and produce other functions rather than data.

A feature model of meta-function domain is presented in Fig. 4.2.

Fig. 4.2
figure 2

Feature model of meta-function domain

From the feature model in Fig. 4.2, we can observe that in total, there can be six different variants of meta-functions available, which are summarized in Table 4.1.

Table 4.1 Summary of meta-functions

A formal definition of the meta-functions in functional homogeneous meta-programming domain follows below.

Nomenclature:

  1. 1.

    Let \( f \) be a function that maps from its domain to its range as follows: \( f:D \to R \), where \( D \) is a domain and \( R \) is a range. Domain and range are sets in mathematics and types in programming languages.

  2. 2.

    Let \( p \) be a metric function that maps a function to its property value as follows: \( p:f \to N \), where \( N \) is a set of property values.

  3. 3.

    Let \( \left\{ f \right\} \) be a set of functions.

  4. 4.

    Let \( f_m^1 \) be a first-order meta-function (or meta-function, in short).

  5. 5.

    Let \( f_m^2 \) be a second-order meta-function (or meta-meta-function, in short).

A formal description of different flavours of meta-functions is as follows:

  1. 1.

    Function transformation: \( f_m^1:\left( {D \to R} \right) \to \left( {D \to R} \right) \) or, more simply, \( f_m^1:{f_1} \to {f_2} \), \( {f_1},{f_2} \in \left\{ f \right\} \).

  2. 2.

    Function domain shifting: \( f_m^1:\left( {D \to R} \right) \to \left( {D^\prime \to R} \right) \), where \( D^\prime \subset D \) or \( D^\prime \supset D \).

  3. 3.

    Function range shifting: \( f_m^1:\left( {D \to R} \right) \to \left( {D \to R^\prime} \right) \), where \( R^\prime \subset R \) or \( R^\prime \supset~R\).

  4. 4.

    Function shifting: \( f_m^1:\left( {D \to R} \right) \to \left( {D^\prime \to R^\prime} \right). \)

  5. 5.

    Function composition: \( f_m^1:\left( {\left\{ f \right\}} \right) \to {f_1}\left( {{f_2}\left( {...} \right)} \right) \).

  6. 6.

    Optimal function selection: \( f_m^2:\left( {\left\{ f \right\},p:f \to N} \right) \to {f_i} \), \( {f_i} \in \left\{ f \right\} \) such as \( p\left( {{f_i}} \right) \leq p\left( {{f_j}} \right) \) (or \( p\left( {{f_i}} \right) \geq p\left( {{f_j}} \right) \)), \( \forall {f_j} \in \left\{ f \right\},i \ne j \).

As we have defined meta-functions as the main concept in the functional meta-programming domain, we can proceed one step higher above the meta-programming domain into the meta-meta-programming domain. In functional meta-meta-programming, the main concept is a meta-meta-function. By analogy, we define a meta-meta-function as a portion of meta-meta-program code that performs a calculation over its inputs (function(s) or a meta-function) returns a function or a meta-function.

The main difference of a meta-meta-function from a meta-function is that a meta-function must be either accepted as an input or produced as an output by a meta-meta-function. Therefore, meta-meta-functions operate on the meta-function domain rather than on type or function domains: meta-meta-functions process and/or produce other meta-functions rather than functions or data.

A feature model of the meta-function domain is presented in Fig. 4.3. From the feature model in Fig. 4.3, we can see that in total, there can be five different variants of meta-meta-functions, which are summarized in Table 4.2.

Fig. 4.3
figure 3

Feature model of meta-meta-function domain

Table 4.2 Summary of meta-meta-functions

A formal description of different flavours of meta-meta-functions is as follows:

  1. 1.

    Meta-function finding: \( f_m^2:\left( {{f_1},{f_2}} \right) \to f_m^1 \) such as \( f_m^1:{f_1} \to {f_2} \). Note: this is inverse of function transformation.

  2. 2.

    Meta-function transformation: \( f_m^2:f_m^1 \to f_m^1 \).

  3. 3.

    Meta-function composition: \( f_m^2:\left( {f_m^1,f_m^1} \right) \to f_m^1\left( {f_m^1} \right). \)

  4. 4.

    Function lifting: \( f_m^2:\left\{ f \right\} \to f_m^1 \).

  5. 5.

    Meta-function evaluation: \( f_m^2:\left\{ {f_m^1,{f_1}} \right\} \to {f_2} \).

3.2 Structural Homogeneous Meta-Programming

First, we begin with the definitions. The main concept of structural programming is a type (a class in an object-oriented programming is just a derivative concept from a type). Where can be numerous definitions found in scientific literature of what a type is? For example, a type has been defined as ‘an abstract data type consists of a signature corresponding to the first, syntactic level and a set of axioms corresponding to the second, semantic level’ [Sch03]. Classes may be defined as algebraic structures that consist of one or more sets of variables (attributes) closed under one or more operations (methods), satisfying some axioms.

In homogenous structural meta-programming, the main concepts are polymorphic types, that is, types (classes) parameterized by constant values or other types known at compile time. Such polymorphic types can operate uniformly and transparently on a set of data values, which exhibit some common structure [CW85], identically without depending on the specific identification of their type. One common feature of parametric polymorphism is that type information plays no computational role, that is, no computational decision is made in terms of types and they are only used by the compiler for type checking [Rey83].

A specific case of polymorphic type is a meta-type, or a ‘type of types’. Meta-types are called ‘kinds’ [Pie02] in formal functional programming theory. A kind is the type of a type constructor or, less commonly, the type of a higher-order type operator. Unlike types, kinds are purely structural: they simply reflect the kinds of the type parameters, which a type expects [MPO07]. Pierce [Pie02] uses kinds to classify types and type operators as follows:

  •  *     is the kind of proper programming types like ‘Boolean’.

  • * → *    is the kind of type operators (functions from types to types).

  • * → * → *   is the kind of functions from types to type operators.

  • (* → *) → *  is the kind of functions from type operators to types.

New meta-levels of meta-types can be introduced further. Such systems have been studied under the heading of pure type systems [Bar92] and used in computer science for applications such as theorem proving. Types and meta-types are summarized in Table 4.3.

Table 4.3 Summary of types and meta-types (According to [Igl05])

In homogenous meta-programming using an object-oriented language, meta-types are called meta-classes. A meta-class is ‘a class that defines common properties that all classes as instances of a meta-class should provide’ [Sch01]. Meta-classes are often used as meta-function containers. Based on the analysis, the following feature model of meta-type domain can be defined (see Fig. 4.4).

Fig. 4.4
figure 4

Feature model of meta-type domain

3.3 Mixed Homogeneous Meta-Programming

In practice, pure functional or pure structural meta-programming is rarely used. More commonly, the concepts from both domains are combined so that we can talk about mixed meta-programming, where both meta-functions and meta-types are used in the same meta-program. A feature model of mixed meta-programming domain is presented in Fig. 4.5.

Fig. 4.5
figure 5

Feature model of mixed homogeneous meta-programming

Based on this feature model, a definition of mixed homogeneous meta-programming can be given as follows: a meta-program is a specific composition of meta-types, where each meta-type (or meta-class) contains generic data and functions (or meta-function s) over this data to implement a specific algorithm at compile time.

Further, we proceed to a more extensive description of meta-functions and meta-meta-functions with their implementation techniques described and specific examples in Java given. But first, we begin with the description of capabilities for homogeneous meta-programming in Java.

4 Homogeneous Meta-Programming in Java

4.1 Basic Built-In Abstractions

Java provides these built-in abstractions for homogeneous meta-programming (which is called as ‘generic programming ’ in Java technical specifications):

A generic type variable is an unqualified identifier that can be introduced and used in generic class declarations, generic interface declarations, generic method declarations and generic constructor declarations. These type variables are known as the type parameters.

A generic interface declaration defines a set of types, one for each possible invocation of the type parameter section. All parameterized types share the same interface at run time. An example of generic interface declaration is:

public interface F<A, B> {

public B f(A a);

}

This interface says that for any two types, A and B, there is a function (called f) that takes an A and returns a B. When this interface is implemented, A and B can be any types, as long as the programmer provides a function f that takes the former and returns the latter. An example of the implementation of this interface is:

F<Integer, String> intToString = new F<Integer, String>() {

public String f(int i) {

return String.valueOf(i);

}

}

A generic class declaration defines a set of parameterized types, one for each possible invocation of the type parameter section. All of these parameterized types share the same class at run time. An example of generic class declaration is:

//container that can hold an object of any type T

class Container<T> {

T object;//declare an object of type T

Container(T obj) {

object = obj;

}

T getObject() {

return object;

}

}

A generic method defines a set of parameterized methods that differ by the type of the parameters of the method. Generic methods also can have the type of the returned value parameterized, though they cannot differ only by the type of the return value. The form of the type parameter list is identical to a type parameter list of a class or interface. An example of a generic method declaration is presented in Java below:

//swaps two variables of a type T.

static void Swap<T>(T a, T b) {

T tmp = a; a = b; b = tmp;

}

Further, we continue with taxonomy of homogeneous meta-programming techniques in Java.

4.2 Taxonomy of Homogeneous Meta-Programming Techniques in Java

The taxonomy of homogeneous meta-programming techniques presented in Fig. 4.6 is based on the analysis of Java language syntax and different applications of generic Java capabilities.

Fig. 4.6
figure 6

Taxonomy of homogeneous meta-programming

Further, we proceed with and analyse elements of this taxonomy, including capabilities of Java or C++ to implement them.

4.3 Functional Homogeneous Meta-Programming Techniques

Here, we consider the following functional homogeneous meta-programming techniques: a type constructor, a monad transformer, an expression template, and a combinatory.

A type constructor (or type generator) is a function that takes a list of types and returns a type. Typical application of this feature is to parameterize a piece of code by a generic data type (a data type which is itself parameterized by a type). For example, the Java class List of the standard library is a type constructor; it can be applied to a type T with the syntax List <T> to denote the type of lists of T:

static void <T> List<T> listConstructor(T value) {

// return an object of List of T

return new List<T>(value);

}

Expression templates have been introduced by Veldhuizen [Vel01] and Vandevoorde [VJ02] in C++. When applied to a vector (or array) class, expression templates allow writing arbitrarily complex vector expressions. A parse tree of the expression is created. The parsing is done at compile time of the parse tree as a template type. When a parse tree is assigned to an array, a function similar to the presented below (in C++) is called:

struct plus { }; // represent addition

class Array { }; // represent a node in parse tree

template <typename Left, typename Op, typename Right> class X { };

template <class T> X<T, plus, Array>

operator+(T, Array) {

return X< T, plus, Array>();

}

// example of usage

Array A, B, C, D; D = A + B + C;

The technique can be applied for (1) compile-time domain-specific checks on the structure of expressions, which the type system of a target language cannot express otherwise; (2) compile-time optimization transformations and custom code generation for expressions and (3) development of embedded domain-specific languages [CDS+03].

The technique can be implemented in Java too, though it is not as elegant and usable, since there is no operator overloading in Java.

Monad transformer is a type constructor (monad) which takes a monad as an argument and returns a monad as a result. For a description of a monad, see Sect. 4.4.4 below. Monad transformers can be used to compose features encapsulated by monads – such as state, exception handling and I/O – in a modular way. Typically, a monad transformer is created by generalizing an existing monad; applying the resulting monad transformer to the identity monad yields a monad which is equivalent to the original monad. Formally, a monad transformer is a type constructor with kind (* → *) → (* → *) (the notation introduced by [Pie02] is used). An example of monad transformer for transforming Value monads from one type of data to another is presented in Java below:

// monad

final static Monad<Value> m = new Monad<Value>() {

public <A> Value<A> pure(final A a) {

return new Value<A>() {

public A value() {return a;}

};

}

public <A,B> Value<B>

bind(final Transf<A, Value<B>> t, final Value<A> a) {

return new Value<B>() {

public B value() {

return t.transform(a.value()).value();

}

};

}

};

// monad transformer

final static Transf<Integer, Value<String>> t =

new Transf<Integer, Value<String>>() {

public Value<String> transform(final Integer val) {

return new Value<String>() {

public String value() {return val.toString();}

};

}

};

Monad transformers, for example, have been used for program slicing [ZX05].

A fixed-point combinator (or fixed-point operator) is a higher-order function that computes a fixed point of other functions. A fixed point of a function f is a value x such that f(x) = x. Fixed-point combinators have an interesting property that any functional program can be written in a form that is free of variables using simple combinator functions [CF58].

A large number of combinator functions have been proposed and analysed, a full list can be found in [Smu85]. For example, the B-combinator performs composition of functions, the C-combinator serves for exchange of the arguments of a dyadic function, the K-combinator discards the second of its two arguments and returns its first argument, and the Y-combinator enables recursion and abstracting its implementation when a function cannot be called from its body. An example of implementation of the K-combinator in Java using a functor and a meta-function is presented below:

interface Fun<X, Y> {

Y apply(X x);

}

public static <A, B> Fun<A, Fun<B, A>> k() {

return new Fun<A, Fun<B, A>>() {

public Fun<B, A> apply(final A a) {

return new Fun<B, A>() {

public A apply(final B b) {return a;}

};

}

};

}

Combinators have been applied, for example, for functional genetic programming with combinator operators [BN06].

4.4 Structural Homogeneous Meta-Programming Techniques

Structural meta-programming programming allows the programmer parameterize his or her data structures and algorithms by types. The general term for this mechanism is parametric polymorphism [Rey83]. It allows the usage of the same fragment of code with different type instances. Further, we present examples of using class templates, functors, mixins, mixin layers, traits, monads, comonads and arrows in Java.

A template is a type or function parameterized over a set of types, functions or constants [CE00]. Container class templates are typical examples of parameterization of a class by a type. The example presented below shows a Vector class parameterized by size and by element type in C++:

template<class T, int size>

class Vector {

private : T values[size];

};

// a one-dimensional vector of three integers

Vector<int, 3> v;

// a two-dimensional 100-by-100 matrix of doubles.

Vector<Vector <double, 100>, 100> matrix;

Programming using templates is usually called template meta-programming [Vel95]. Meta-programs can be written using template specialization as a conditional operator and template recursion as a repetitive operator. Template meta-programming provides the necessary foundation for developing configuration generators [CE00].

A functor is a programming construct that allows an object to be invoked or called as though it were an ordinary function, usually with the same syntax. In purely object-oriented languages, functors in the context of homogeneous meta-programming are used to encapsulate generic functions because functions cannot appear in the text of a program on their own. An example of a functor in Java is presented below:

// Functor for non-void methods with 2 parameters

public interface Func2 <T1,T2,R> {

public R call(T1 t1, T2 t2);

}

Func2<String,String,Integer> func =

new Func2<String,String,Integer>()

{

public Integer call(String t1, String t2) {

// here is the implementation of functor

}

};

A mixin is a class that provides a certain functionality to be inherited by a subclass and thus allows to implementing composition of functionality [BC90, SB00]. A class may inherit most or all of its functionality from one or more mixins through parameterized inheritance. A mixin can be represented as a class template derived from its generic parameter, for example, in C++:

template<class Base>

class Printing : public Base

{…}

Mixins based on parameterized inheritance in C++ have been used to implement highly configurable collaboration-based and layered designs [SB98].

Mixin layers are mixins that encapsulate other mixins [SB02]. This technique is used to build entire collaborations as components as follows:

template <class Super>

class MixinLayer: public Super {

public:

class InnerClass: public Super::InnerClass {…};

};

Composing mixin layers to form concrete classes is as simple as composing mixin classes. If we have four mixin layers (Layer1, Layer2, Layer3, Layer4), we can compose them as follows:

Layer4 < Layer3 < Layer2 < Layer1 > > >

Mixin layers have been used to implement a graphical user interface library (in Java) that can be configured to run on platforms with widely dissimilar capabilities [CBM+02] and to build product lines [SB99]. Applicability of the approach for the real-time and embedded systems has been demonstrated by [ASB04].

Java supports mixins only partially using interfaces. However, an interface only specifies what the class must support and cannot provide an implementation. Another class, providing an implementation and dependent with the interface, is needed for re-factoring common behaviour into a single entity [BSK02].

Traits were originally introduced by Scharli et al. [DNS+06] as a mechanism for sharing common method definitions between classes. As such, traits allow structuring of object-oriented programs: traits are used as units of reuse to compose classes from a set of methods [Mye95]. Traits can be composed from other traits, but the composition order is irrelevant. When, combined with meta-programming techniques, traits can be used to build classes at compile time. The technique is called trait-based meta-programming [RT06]. It provides a convenient way to associate related types, values and functions with a template parameter type without requiring that they be defined as members of the type.

Java currently does not support the traits technique. However, several extensions of Java, such as Scala, do support it. An example of trait in C++ is presented below. This example demonstrates how a trait can be used to find a type of an array [MC00]:

template <typename T>

struct remove_bounds

{typedef T type;};

template <typename T, std::size_t N>

struct remove_bounds<T[N]>

{typedef T type;};

Traits have been used, for example, to implement product lines [BDS10] and the C + meta-programming library Boost [GA03].

A monad is a kind of the abstract data type constructor used to represent computations (instead of data in the domain model) [Wad92]. Each monad represents a different type of computation. Monads allow the programmer to chain actions together to build a pipeline, in which each action is decorated with additional processing rules provided by the monad [ER04]. A monad can be thought of as a container that contains values and has the following three operations that must fulfil several properties to allow the correct composition of monadic functions (i.e. functions that use values from the monad as their arguments):

  1. 1.

    A type constructor M.

  2. 2.

    The unit operation takes a value from a plain type and puts it into a monadic container of type M.

  3. 3.

    The bind operation performs the reverse process, extracting the original value from the container and passing it to the associated next function in the pipeline.

An example of monad implementation in Java is presented below:

public static <A> Callable<A> unit(final A a) {

return new Callable<A>() {

public A call() {return a;}

};

}

public static <A, B> Callable<B>

bind(final Callable<A> a, final F<A, Callable<B>> f) {

return new Callable<B>() {

public B call() {return f(a.call()).call();}

};

}

A programmer may define a data-processing pipeline by composing monadic functions, whereas the monad acts as a reuse framework that performs all the ‘dirty’ composition work. In practice, monads can be used to (1) model programs with computational effects such as state, exceptions and continuations and (2) extend language definitions in a modular way [Mog91, Wad98] and multi-stage programming [CK05].

Comonad is a monad that represents a kind (i.e. a meta-type) of context [UV08]. Comonads are defined by:

  1. 1.

    A type constructor

  2. 2.

    The extract function that extracts a value from its context

  3. 3.

    The extend function that may be used to compose a pipeline of ‘context-dependent functions’

The two most common comonads are the identity comonad, which maps type T to itself, and the product comonad, which maps type T into tuples of type C, where C is the context type of the comonad.

An example of comonad in Java (taken from a tree class in the Functional Java library) is presented below:

// apply a function to all subtrees of a tree,

// and return a tree of results

public <B> Tree<B> cobind(final F<Tree<A>, B> f) {

return

unfold(new F<Tree<A>, P2<B,P1<Stream <Tree<A>>>>>(){

public P2<B, P1<Stream<Tree<A>>>>

f(final Tree<A> t) {

return P.p(f.f(t), t.subForest());

}

}).f(this);

}

// Expands this tree into a tree of trees

public Tree<Tree<A>> cojoin() {

final F<Tree<A>, Tree<A>> id = identity();

return cobind(id);

}

Comonads provide a means to structure context-dependent notions of computation such as dataflow computation (computation on streams) and for tree relabelling as in attribute evaluation [UV08].

Arrow is a generalization of monads [Hug00]. Both monads and arrows are generic interfaces for constructing programs using combinators. Arrows relax the stringent linearity imposed by monads while retaining a disciplined style of composition [Pat03]. Arrows allow to write asynchronous programs in modular units of code, and to compose them in many different ways, while nicely abstracting the details of composition [KHF+09]. Arrows as a programming abstraction and their transformations were extensively analysed by [LCH09] (also see Fig. 4.7).

Fig. 4.7
figure 7

Commonly used arrows (According to [LCH09])

An example of arrow demonstrating function composition is given in Java:

public static <A,B,C> F<A,C>

compose(final F<B, C> f, final F<A, B> g) {

return new F<A, C>() {

public C f(final A a) {return f.f(g.f(a));}

};

}

Arrows have been used for managing information security, confidentiality and integrity policies by appending a tag (implemented as an arrow) to the output of the computation we want to protect [LZ10]. At compile time, security-typed programs are checked to guarantee that there is no information flow from higher security levels to lower security levels; violations of this information-flow policy result in type errors. Other applications of arrows are as a domain-specific embedded language [GN08], parsers and printers [JJ99], parallel computing [HHP07], multi-stage programs [Meg10], etc. Arrows were also used to implement the exponential function, a sine function with fixed and variable frequency, a sound synthesis program and a robot simulator [LCH09].

5 Homogeneous Meta-Programming in VHDL

This section is about a systematic structural homogeneous meta-programming in VHDL, a domain-specific language for hardware design. By systematic meta-programming, we mean programming in VHDL with the prescribed aim of generalization in order to enhance a component-based and generative reuse. Such an approach is different from ad hoc programming in VHDL when a designer seeks to write code that is oriented to use in a specific narrow design, where other requirements (e.g. optimization) but not generalization are at the focus. What is common for both paradigms (meta-programming and ad hoc programming in VHDL) is the use of the same language but in different modes. It is not our intention to provide in detail the syntax of the language in order to explain the essence of homogeneous meta-programming. What we need in our context for consistency of the topic is to define some specific terms and describe the basic features of the language.

5.1 Similarities and Differences Between VHDL and General-Purpose Programming Languages (GPLs)

VHDL is a topic related to an abstract dealing with the domain of digital systems. The digital systems cover a wide range of hardware from the lower-level gates that make up the components to the top-level functional units. VHDL is a domain-specific language aiming to describe these systems. VHDL is now an industry-wide standard language used to document a design and to provide a specification for simulation, verification and synthesis of electronic systems.

The language has many features that can be found in other high-level languages, for example, programming languages, such as Pascal or C++. However, VHDL has as well many discriminating characteristics by which it differs from general-purpose programming languages (GPL). Similarities are as follows:

  • VHDL has types common to most GPLs.

  • VHDL has objects (constants and variables) common to most GPLs, such as PASCAL or C.

  • VHDL has sequential statements similar to those used in other languages.

Differences are as follows [Ash08, Sal98]:

  • VHDL is a domain-specific language to support hardware design, modelling and synthesis.

  • VHDL program is an abstraction having the clearly defined meaning as a piece of hardware.

  • VHDL has signals, the domain-specific objects, to support modelling concepts.

  • VHDL has concurrent statements to support a concurrency in hardware modelling using signal and event concepts.

  • VHDL has constructs to support a hierarchical composition of a design.

  • VHDL has the physical data type specific to the design domain.

The description in VHDL has a well-established meaning. This solution always should be thought of as a virtual or abstract piece of hardware consisting of building blocks called components. At the physical level, components are interconnected with other components of a system with wires. Wires are the media to transfer information, the signals produced either inside or outside the system, from one component to another. The whole system has a well-established structure, and this structure also is a higher-level component with its interface and functionality. These terms are fundamental in describing digital systems. VHDL models are well-established components to demonstrate structural (component-based) reuse.

5.2 Component Generalization Framework in VHDL

Figure 4.8 outlines a framework for generalization [Zib01]. It can be seen as a scheme presenting a model-based view to the homogeneous meta-programming framework given in Fig. 4.1 (it describes meta-programming from the language operations viewpoint).

Fig. 4.8
figure 8

A model-based framework for generalization

At the core of the model-based framework is the generic component model in VHDL. VHDL component model consists of two interrelated parts: entity description and architecture description. In this context, the generic model means that each part of the component model is to be generalized in somewhat manner according to the given set of requirements, applied mechanisms and abstractions. Note that specification of requirements for generalization is a matter of domain analysis that is not considered here.

There are two basic mechanisms to support generalization in VHDL: parameterization through the use of generic constants and implicit separation of concepts. In case of VHDL, implicit separation of concerns means dividing VHDL constructs (by a designer who intends to use the paradigm) into two sets, which in Fig. 4.1 are identified as higher-level constructs and lower-level constructs. Lower-level constructs serve for expressing the basic functionality, while the former serves for expressing generalization aspects.

The real content of generalization is to be introduced through the use of language abstractions applied. As VHDL is the IEEE standard, there is a great deal of abstractions to express either lower-level functionality or generalization aspects. We restrict ourselves by mentioning some higher-level constructs to express generalization in VHDL (see [Ash08], for the full list of constructs). The list of higher-level constructs is as follows: library, use, package, generic, for… generate, if… generate, etc.

Note that these constructs are conventional language constructs. The only discriminating feature of the constructs is the mode and the context of their use, as it will be shown by our case study later.

6 Case Study: Development of Generic GATE Component in VHDL

6.1 Formulation of Requirements

For our case study, we have selected the gate-level component because this level is fundamental in the given domain (i.e. higher-level components such as adders and multiplexors are composed from gates). As the introduced framework indicates (see Fig. 4.8), we need to start from the formulation of requirements. The requirements are as follows:

  1. 1.

    The generic GATE component must be a single component that covers all logical functions (operations) applicable in VHDL: and, or, xor, nand, nor, xnor and not.

  2. 2.

    The component must have as many inputs as it requires in a given design (from 2 to any), except for negation (i.e. not operation), which always has one input.

  3. 3.

    Input must be a vector, output must be a scalar both of the conventional type std_logic (note that this type means a multi-valued logic used in hardware modelling, which is compatible with the type bit) and any operation is to be performed between the elements of the vector for all operations, except the negation operation.

  4. 4.

    The input vector must be of the size (width) greater than 2 for all operations, except the negation operation (its input is of one-bit length).

  5. 5.

    The detection of the violation of the requirement #4 must be implemented.

  6. 6.

    All parameters should be introduced through generic constants for uniformity of the interface.

Note that the requirement #3 can be also treated as a constraint because there is no other way to generalize the signal width in VHDL as using vectors.

6.2 Development of Generic Interface

A homogeneous meta-program in VHDL consists of the generic interface and the generic architecture. Here, we consider the development of a generic interface. The possible solutions could be used as follows: (1) to use the entity description and generalize it using generic constants and specific types (e.g. introduced by a designer and saved in a package) and (2) to use the only package as a higher-level interface that describes the items (e.g. types and component) needed for generalization. The latter possibility is legal because the entity and component descriptions are similar by syntax, and furthermore, they are compatible semantically, that is, instead of the entity description, we can use its analogue, the component description in a package. Both capabilities are illustrated by examples below. For example, Fig. 4.9 explains the implementation of the first capability, where requirements 1–4 are implemented using generic constants n and op (meaning the width of the array, i.e. the number of inputs, and the kind of an operation, respectively) as higher-level parameters.

Fig. 4.9
figure 9

Generic interface of generic gate component

The initial parameter values are also specified (n = 2 and op = l_and, see Fig. 4.9). Though generic parameters have types, the type matching is not always sufficient for the validity of the parameters because the parameters relate with each other and often certain combinations of parameter values are not allowed. For instance, the combination of values n = 2 and op = l_not is not valid. Therefore, the validity of the parameters and their combinations can be checked in the entity statement part. The checking rules must be specified using the concurrent assertion statements as early as possible, for example, in the generic interface as it is demonstrated in Fig. 4.10.

Fig. 4.10
figure 10

Generic interface with assertion of parameter value compatibility

The second capability of the generic interface is demonstrated in Fig. 4.11. This description has the following advantages in contrast to the entity description (see Fig. 4.9). Firstly, it is a unified description visible at the highest level (note that visibility is expressed trough constructs library and use) (see, e.g. Fig. 4.11). Secondly, new types are usually defined in packages. Therefore, the user can see the definitions of the types that are used in the interface. Finally, the package contains the abstract component description. The abstract component description is usually the exact copy of the entity description. If an abstract component description is in the package, then it is no longer required in the declarative part of the architecture, where the component is used. However, the unified description of the generic interface has a restriction: there is no possibility to use assertion statements as in the case that is demonstrated in Fig. 4.10.

Fig. 4.11
figure 11

Another solution to develop generic interface of generic gate

When seeing the package, the designer knows how to connect the component into a system. Therefore, the package plays the role of the generic interface in VHDL. The interface helps the user to instantiate the component by specifying generic parameter values. In VHDL, the instantiation occurs at the time, when composition of the component into a higher-level system is performed.

6.3 Development of Generic Architecture

A generic architecture is a description of the architecture in VHDL that implements generalization aspects of the gate functionality specified in the requirements statement. In other words, the functionality of all logical function is to be described taking also into account the variable number of inputs. A template of the generic architecture is outlined in Fig. 4.12.

Fig. 4.12
figure 12

Template to implement generic architecture of generic gate component

The template is represented as a composition of the concurrent ifgenerate statements, each containing within the process statement for all possible logic operations (for simplicity reasons, the process statement is shown in Fig. 4.12 only for the first operation, i.e. the operation l_and). Though the description encompasses all possible variants of gate functions, the only one function, which corresponds to the instantiated parameter value, is activated, when the model is executed. The process statement describes the implementation of both aspects of generalization: the logical function and the number of its inputs. What is important to note is that there are several possibilities for implementation. One solution (if op = l_and, see Fig. 4.12) is given in Fig. 4.13.

Fig. 4.13
figure 13

Generalization with respect to the number of inputs

The template given in Fig. 4.12 to implement the generic architecture is redundant because the process statement is used multiple times. A better solution is described in Fig. 4.14, where the process statement is used only once the case statement enables to specify the needed function through the value of generic parameter op.

Fig. 4.14
figure 14

Generic architecture implementation using one process statement and a set of sequential statements within

6.4 How Generic Component Is Used to Compose a Higher-Level Component/System

There is a variety of higher-level components that use gates as entities for composition. A typical example is the one-bit half adder, the structure and functionality of which is described using two logical equations in the VHDL signal notation as follows:

$$ \begin{gathered} \mathit{sum} < = x\;\mathit{xor}\;y; \\[-4pt] \mathit{carry} < = x\;\mathit{and}\;y; \end{gathered} $$

where ‘<=’ is the signal assignment notation in VHDL, x and y are inputs and sum and carry are outputs of the half adder.

Figure 4.15 presents the full description for implementing the one-bit half adder in VHDL using generic gate. It is assumed that the interface of the generic gate is saved the package pack_gate, and its generic architecture is described using the template given, for example, in Fig. 4.14.

Fig. 4.15
figure 15

Implementation of one-bit half adder using generic gate

7 Summary

This chapter presented a comprehensive overview of the structural and functional homogeneous meta-programming techniques. Two case studies were considered: (1) based on the general-purpose programming language Java and (2) based on the domain-specific programming language VHDL. The formal foundations of meta-types and meta-functions were explained. The main concepts were described, and examples in VHDL and Java were presented. An extensive case study in VHDL has explained the main principles of the development and usage of structural heterogeneous meta-programs in the domain-specific language VHDL.

8 Exercise Questions

  1. 4.1.

    What are the language-independent aspects of homogeneous meta-programming? Which aspects and how are these implemented in Java, and which in VHDL?

  2. 4.2.

    What are the fundamental differences between functional and structural homogeneous meta-programming?

  3. 4.3.

    What is the difference between function and meta-function? Provide an example in Java.

  4. 4.4.

    What is the difference between type and meta-type? Provide an example in Java.

  5. 4.5.

    What is the difference between generic class and template. Provide examples in C++ and/or Java.

  6. 4.6.

    Write a monad for a simple arithmetic calculator that returns expression templates rather than results of expression calculation.

  7. 4.7.

    Write an identity comonad in Java.

  8. 4.8.

    Write a loop arrow (see Fig. 4.7) in Java.

  9. 4.9.

    List all homogeneous meta-programming constructs available in VHDL.

  10. 4.10.

    Develop a generic full ADDER in VHDL (you may use a generic half adder (see Fig. 4.15) as a component).