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 demo (start/DRAFT)” 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 supports Generics – most do).

The definitions

As “being a generic” isn’t visual in the definition, let’s compare two definition in The Sieve demo (start/DRAFT). That of the Slowstart protocol, and the Sieve component.

Both are defined with a formal argument (aka a parameter). Although it is needed to have (at least) a parameter, this does not reveal one is (used as) a Generic. A list of (one of more) TypedParameters just give 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) in 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 like:

- 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 have the ability to fix some parameters of existing functions to create a new function with less parameters. Conceptually, it is a function that calles another function (with more parameters), without writing-out that function completely.

The Castle approach to Generics is very simular: A SlowStart parameters is “fixed” (we speak about ‘bound’) such that the derived SimpleSieve protocol doesn’t need to 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