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