Generics: Parameters, Wrappers and template-specialisation

Castle supports Generics (a bit like Templates in C++) but with a twist. For example, in (the improved version of) “The Sieve (basic variant)” we use the SlowStart (base)protocol as a Generic protocol.

It becomes generic as we pass an Argument to the base class. Only that makes SlowStart a generic!
It is not visual in the definition.

As this differs from other languages, it gives some questions. We will explain how to use it. And make some (high-level) hints on the implementation.

SlowStart & Sieve

Warning

In this example, one is a Protocol, and the other is a Component. Nonetheless, that is an irrelevant detail – the same applies to other constructs (that support Generics – most do).

The definitions

As “being a generic” isn’t visual in the definition, let’s compare two definitions in The Sieve (basic variant): The Slowstart protocol and the Sieve component.

Both have a (one) formal argument (aka a parameter) in their definition. Without a hint that one is being used as a Generic –although a (at least one )parameter is essential. The list of TypedParameters just gives the option to pass a value(s); here an int in both cases. – like we can in most languages when we instantiate a class.

protocol SlowStart(queue_max:int): EventProtocol {
 setMax(queue_len:int);
}
component Sieve(onPrime:int) : Component {
  port SimpleSieve<in>:try;
  port SimpleSieve<out>:coprime;
}

Instantiate vs Specialise

Nevertheless when using the SlowStart protocol –here when defining the SimpleSieve protocol– it becomes clear that SlowStart is (used as) a Generic. A Parameter (here: ‘1’) is bound to a parameter during the definition of SimpleSieve.

protocol SimpleSieve : SlowStart(1) {
   input(int:try);
}

By passing a (one or more) parameter(s) when “subclassing”, we kind of specialise the Generic into a “wrapper class” in between the (real) base class and the derived class. This in-between protocol (often denoted as Base(1) or Base_1) is not instantiated –it still acts as a “class”. And so, can be subclassed.
It acts exactly as the (real) base class, except that the bound parameter has become build-in – kind of what you expect.

The Sieve Component is used completely dissimilar (or: more traditionally). It is instantiated somewhere in executable code, as we see below.

SimpleSieve.input(newPrime) on self.finder.found
{
  alias s;

  // Extent the sieve list ...
  s:= Sieve.new(newPrime); // See caution, below
  ...

Caution

The code above uses the code-snipped: s:= Sieve.new(newPrime), with the new() method. That syntax detail is fully stable yet. It could be that it becomes s:= Sieve(newPrime) (as in Python), or …

The difference

The difference is clear:

  • When the protocol/component is “new’d”, or “called”, we instantiate.
    And we get an element (aka a class instance).

  • When we bound a parameter during a definition, we specialise.
    The result is like a “class”.

Another view

Instantiate generic

You may wonder, what happens as we instance a Generic. For example:

- strangeDemo(self, ...)
{
...
p =  SlowStart(42) // Remember: SlowStart is (also, used as) a Generic.
...
}

This is allowed! Now, p [1] has become an instanced protocol (aka an object-instance), where queue_max is initialised to ‘42’.

Partials (aka closure)

Some languages support the functional programming feature “partial(s)” and/or the related “closure” construct. Both can fix some parameters of existing functions to create a new function with fewer parameters. Conceptually, it is a function that calls another function (with more parameters), without writing out that function completely.

The Castle approach to Generics is very similar: One SlowStart parameter is “fixed” (we speak about ‘bound’) such that the derived SimpleSieve protocol doesn’t need to be set in when it is initialised.
In the SlowStart//SimpleSieve example, we could define a SlowStart1 protocol by reusing the general SlowStart protocol but hardcoding the initial values to 1. And derived from that SlowStart1 protocol to define SimpleSieve.

// This is all pseudo-code -- not valid Castle syntax
protocol SlowStart1 (SlowStart);
implement SlowStart1::
- init(self) {
  super.init(1)
}

And, we can do the same for SlowStart2, SlowStart3, ...42, etc.
Or, we can automate that. By binding that parameter by specialising the Generic when we need it.

The effect is the same. Only Castle will generate that code for you.

Footnotes

Comments

comments powered by Disqus