Component based programming in D

Marco de Wild mdwild at sogyo.nl
Tue Jun 18 19:03:16 UTC 2019


On Tuesday, 18 June 2019 at 09:17:09 UTC, Bart wrote:
> I'm new to component based programming. I've read it is an 
> alternative to oop for speed. I don't understand how it is 
> possible to have an alternative to oop and still have oop like 
> behavior(polymorphism) nor how to do this. It seems all the 
> great things oop offers(all the design patterns) would be 
> impossible. The benefits are suppose to better reusability as 
> components are more isolated in their dependencies.
>
> Can someone help me understand this a little better and how I'd 
> go about using it in D? Specifically I'm looking at the pros 
> and cons, what are the real similarities and differences to 
> oop, and how one implements them in D(taking in to account D's 
> capabilities).
>
> Thanks.

I don't know a lot about component-based programming per se. (I 
might use it all the time, I just don't know the term.)

OOP as implemented by Java, C# etc. use virtual functions and 
inheritance to compose behaviour, also known as polymorphism. 
This has a slight overhead at runtime. In like >90% of the cases 
however, you can deduce statically what override/implementation 
of a function will be called. In D, we have the means to make 
that deduction at compile time.

The most simple means is duck typing:

int fun(T)(T instance)
{
     return instance.number();
}

We have a templated function, that is: a function that takes an 
instance of any type. Think of generics, but more liberal. If we 
call the function with an object:

class Dice
{
     final int number()
     {
         return 6;
     }
}

fun(new Dice());

the compiler generates a template instance, that is, a `fun` that 
takes a `Dice` instance as its argument. It will then compile as 
if you have written the Dice type there yourself:

int fun(Dice instance)
{
     return instance.number();
}

This compiles because the class Dice defines a method called 
`number`. However, we can put any type in there that defines a 
number() method. In classic OOP, one would use an interface for 
this

interface Number
{
     int number();
}

class Dice : Number
{
     int number() { return 6; }
}

Using duck typing, the compiler just checks whether the code 
compiles given the type. If we have other types that implement 
number, like a Roulette class, we can simply pass an instance of 
that class to the function and the compiler figures it out. We 
don't need to define an interface.
For large methods, it can be quite unclear what methods a type 
need to define in order to be passed in.

int fun(T)(T instance) // Only if T has number() method
{
     // Large amount of code here
     return instance.number();
}

In this example we can only pass in a T if it defines a 
`number()` method. We annotated it in a comment. However, D can 
also explicitly check it using traits 
(https://dlang.org/phobos/std_traits.html) or the nuclear option: 
__traits(compiles, ...), which checks if a certain expression 
compiles successfully. Doing it the quick and dirty way, we can 
explicitly define our desired instance interface:

int fun(T)(T instance)
     if(__traits(compiles, {int x = instance.number()} ))
{
     // Large amount of code here
     return instance.number();
}

We add a constraint to our template: our function is valid for 
any T for which the number() method returns an integer. This is 
just the surface, you can read 
https://github.com/PhilippeSigaud/D-templates-tutorial for a 
technical introduction.

One thing I really like about this is that we preserve the actual 
type during the whole function. This means that we can call other 
functions using a strongly-typed argument or do other things 
based on what type we get. In my personal project, I need to 
rewrite a bunch of functions that took one type (Card), but now 
need to take another, reduced type (SimpleCard) as well. The 
functions currently output cards grouped in sets. Card has some 
properties (e.g. isFolded, isShown), and SimpleCard is just the 
value representation (e.g. spades-4). I can use classic 
inheritance Card extends SimpleCard, but that means I lose my 
type info along the way. Using duck typing, my functions work for 
any Card-like object and preserve the type as well.
If I need polymorphism, I can achieve that using normal overload 
rules, e.g.

void fun(CardLike)(CardLike card)
{
     foo(card);
}

void foo(Card card) {}
void foo(SimpleCard card) {}
void foo(FakedCard card) {}

The classes can be kept small - new behaviour can be glued to 
classes without having impact on existing code. (I once though 
about why my pet project was progressing so much faster than 
regular projects. I figured because I rarely changed code - I 
just added code in different files and changed one line to 
activate it.)


More information about the Digitalmars-d-learn mailing list