ada_answers_gem_logo_fourth
Next Next

Gem #117: Design Pattern: Overridable Class Attributes in Ada 2012

Let’s get started…

Most object-oriented programming languages provide a facility for declaring variables that are shared by all objects of a given class. In C++, these are called “static members” (and use the “static” keyword), and similarly Python has the notion of “class attributes”.

Let’s consider an example where this is useful. For instance, let’s say we want to define the notion of a block of text that is generated by expanding a template (perhaps after we replace some parameters in that template, as can be done with AWS’s templates parser, for instance). Once we have computed those parameters, we might want to generate multiple outputs (for instance HTML and CSV). Only the template needs to change, not the computation of the parameters.

Typically, such as in Python, the template could be implemented as a class attribute of the Text_Block class. We can then create templates that need the same information but have a different output simply by extending that class:

   class Text_Block(object):
       template = "somefile.txt"
       def render (self):
           # ... compute some parameters
           # Then do template expansion
           print "processing %s" % self.__class__.template

   class Html_Block(Text_Block):
       template = "otherfile.html"

In this example, we chose to use a class attribute rather than the usual instance attribute (self.template). This example comes from the implementation of GnatTracker: in the web server we create a new instance of Text_Block for every request we have to serve. For this, we use a registry that maps the URL to the class we need to create. It is thus easier to create a new instance without specifying the template name as a parameter, which would be required if the template name was stored in the instance. Another reason (though not really applicable here) is to save memory, which would be important in cases where there are thousands of instances of the class.

Of course, the approach proposed in this Gem is not the only way to solve the basic problem, but it serves as a nice example of one of the new Ada 2012 features.

C++, like Ada, does not provide a way to override a static class member, so it would use a similar solution as described below.

Since Ada has no notion of an overridable class attribute, we’ll model it using a subprogram instead (the only way to get dispatching in Ada). The important point here is that we want to be able to override the template name in child classes, so we cannot use a simple constant in the package spec or body.

   type Text_Block is tagged null record;
   function Template (Self : Text_Block) return String;
   function Render (Self : Text_Block) return String;

   function Template (Self : Text_Block) return String is
      pragma Unreferenced (Self);
   begin
      return "file_name.txt";
   end Template;

The parameter Self is only used for dispatching (so that children of Text_Block can override this function). Since we prefer to compile with “-gnatwu” to get a warning on unused entities, we indicate to the compiler that it is expected that Self is unreferenced.

We could make the function Template inlinable, which might be useful in a few cases (for instance if called from Render in a nondispatching call), but in general there will be no benefit because Template will be a dispatching call, which requires an indirect call and thus wouldn’t benefit from inlining.

And that’s it. We have the Ada equivalent of a Python class member.

But so far there is nothing new here, and this approach is rather heavy to write. For instance, the body of Render could contain code like:

   pragma Ada95;

   function Render (Self : Text_Block) return String is
      T : constant String := Template (Text_Block'Class (Self));
   begin
      ..  prepare the parameters for template expansion
      ..  substitute in the template and return it
   end Render;

Fortunately, Ada 2012 provides an easier way to write this, using the new feature of expression functions. Since Template is a function that returns a constant, we can declare that directly in the spec, and remove the body altogether. The spec will thus look like:

   pragma Ada_2012;

   type Text_Block is tagged null record;
   function Template (Self : Text_Block) return String
      is ("filename.txt");
   function Render (Self : Text_Block) return String;

This is a much lighter syntax, and much closer to how one would do it in Python (except we use a function instead of a variable to represent a class member). A child of Text_Block would override Template using the same notation:

   type Html_Block is new Text_Block with null record;
   overriding function Template (Self : Text_Block) return String
      is ("otherfile.html");

Compared to Python, this is in fact more powerful, because some of the children could provide a more complex body for Template, so we are not limited to using the value of a simple variable as in Python. In fact, we can do this in the spec itself, by using a conditional expression (another new feature of Ada 2012):

   pragma Ada_2012;

   type Text_Block is tagged null record;
   function Template (Self : Text_Block) return String
       is (if Self.Blah then "filename.html" else "file2.json");
   function Render (Self : Text_Block) return String;

Finally, we can also make the body of Render slightly more familiar (in terms of object-oriented notation) using the dotted notation introduced in Ada 2005:

   function Render (Self : Text_Block) return String is
      T : constant String := Text_Block'Class (Self).Template;
   begin
      ..  prepare the parameters for template expansion
      ..  substitute in the template and return it
   end Render;

Now the call to Template looks closer to how it would appear in those languages that provide overridable class members. Some will argue that this doesn’t look like a function call and thus is less readable, since we don’t know that we are calling a function. This is a matter of taste, but at least we have the choice.

There is one thing we have lost, temporarily, in the declaration of Template. If we compile with -gnatwu, the compiler will complain that Self is unreferenced. There is currently no way to add a pragma Unreferenced within an expression function. This has generated a discussion here at AdaCore and the issue is not resolved yet. The current two proposals are either to always omit the unused parameter warning when a function has a single parameter and it controls dispatching (precisely to facilitate this class member pattern), or else to use an Ada 2012 aspect for this, as in the following:

   function Template (Self : Text_Block) return String
      is ("filename.html")
   with Unreferenced => Self;

Note also that the use of expression functions in this Gem requires a very recent version of GNAT: the expression function feature wasn’t available in older versions, and the initial implementation had some limitations.

 
 

Comments on Gems are now closed.