www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Design to interfaces or Design to introspections

reply =?UTF-8?B?2LPZhNmK2YXYp9mGINin2YTYs9mH?= writes:
Back at dconf 2015, Andrei introduced design-by-introspection in 
his talk "Generic programing must go", he showed that not only 
classes have "class explosion" problems but so do templates.
The basic idea behind design by introspection the way I 
understand it is similar to the one  behind design patterns. One 
of the Gang of Four' two principles is "Favor object composition 
over class inheritance." i.e. inheritance must go, as Andrei 
would say.

Then I stumbled upon DIP84, which reminded me of the other GoF' 
principle "Program to an interface, not an implementation".
And I started wondering why would I ever write code like this:

     auto someAlgorithm(SomeInterface obj) { /* ... */ }

When I can do this:

     auto someAlgorithm(I)(I obj) if(isSomeInterface!I) { /* ... 
/* }

or more generically:

     auto someAlgorithm(I)(I obj)
     if(satisfiesInterface!(I, SomeInterface, SomeOtherInterface 
/*... etc */)
     { /* body ... /* }

This would be a modern design to introspection? that goes with 
the modern design by introspection.
While this approach has more code over the classic one, It has 
the advantage of working "natively" for both classes and structs 
(without dirty tricks like std.experimental.typecons.wrap does 
for structs), and would in the end save you a lot of code 
duplication, chaos and death if used along with 
design-by-introspection.

What do you think.
Apr 07 2017
parent reply Marco Leise <Marco.Leise gmx.de> writes:
Am Fri, 07 Apr 2017 11:51:10 +0000
schrieb =D8=B3=D9=84=D9=8A=D9=85=D8=A7=D9=86 =D8=A7=D9=84=D8=B3=D9=87=D9=85=
=D9=8A (Soula=C3=AFman Sahmi)
<sahmi.soulaimane gmail.com>:

 [=E2=80=A6]
=20
 Then I stumbled upon DIP84, which reminded me of the other GoF'=20
 principle "Program to an interface, not an implementation".
 And I started wondering why would I ever write code like this:
=20
      auto someAlgorithm(SomeInterface obj) { /* ... */ }
=20
 When I can do this:
=20
      auto someAlgorithm(I)(I obj) if(isSomeInterface!I) { /* ...=20
 /* }
=20
 or more generically:
=20
      auto someAlgorithm(I)(I obj)
      if(satisfiesInterface!(I, SomeInterface, SomeOtherInterface=20
 /*... etc */)
      { /* body ... /* }
=20
 This would be a modern design to introspection? that goes with
 the modern design by introspection.
 [=E2=80=A6]
=20
 What do you think.
Running the same algorithm on class and struct instances is a much desired feature, especially where programmers tend to fall into two camps where one mostly uses classes and the other mostly uses structs. But reasons for using one over the other slip into the concept of one-function-to-rule-them-all as well. And there is more to consider: - Interfaces variables hold references to the data, while for structs you have to explicitly add "ref" to the argument or else the algorithm will work on a (shallow) copy. - Wrapping structs in interfaces and using interfaces exclusively results in only on instance of the algorithm ending up in the executable instead of one per each class and struct type, which helps with things like executable size, instruction cache hits and debug symbol size and readability. - Some forms of licensing break when using templates in the public API of libraries. Two examples: A proprietary software company sells programming libraries. They need to keep their source code private to prevent theft, but if their API used templates they'd have to provide sources for them and any templates used inside of them. On the other hand a GPL licensed open source library - to be usable in a proprietary project - must ensure that none of its code gets compiled into the target application. Again templates would break that. Then there are other general considerations in favor of interfaces or templates. - Template methods are not virtual functions that you can override in a child class. - Calling virtual methods on interfaces is slower (on some architectures more than others)[1] and there is no static introspection to decide some code paths at compile time. (Devirtualization is a hot topic.) - Templates increase complexity for the compiler and runtime. There have been a few subtle issues in the past, for example symbol name length explosion[2] and object files containing old code in separate compilation scenarios[3]. [1] http://eli.thegreenplace.net/2013/12/05/the-cost-of-dynamic-virtual-cal= ls-vs-static-crtp-dispatch-in-c [2] http://forum.dlang.org/post/efissyhagontcungoqkx forum.dlang.org [3] https://issues.dlang.org/show_bug.cgi?id=3D9922 --=20 Marco
Apr 07 2017
parent =?UTF-8?B?2LPZhNmK2YXYp9mGINin2YTYs9mH?= writes:
On Friday, 7 April 2017 at 13:30:00 UTC, Marco Leise wrote:
 [...]
Thank you Marco, you have summarized the issues of both worlds. I guess that's why java' implementation is different, templates in java are synonymous to classes. But that relies on virtualization, and is too restrictive, no primitive types allowed, no structs (java has no structs), and no structural subtyping (the object has to implement the interface). Still I think there has to be a better way to implement templates. as far as speed goes the current implementation is hard to beat, since every thing is flat, there's no overhead. To add to the current implementations I think one way to implement templates or more generally structural subtyping (aka duck typing) would be, to pass metadata (offsets of functions and fields) to functions, the same way you would pass function arguments i.e in a defined order and signature. Every function would declare the metadata it requires for each argument passed i.e which field and function it uses on that object, i.e there's an extra signature (the compiler could take care of that). Pros: - static duck-typing (awesome). - enables binary distribution since one instance suffices. - faster than virtualization, it has less overhead with always only one indirection, to call a function fetch it from the argument list then call it. - replaces virtualization, inheritance could be implemented on top of this. - does not change the syntax of the language, only the compiler needs to worry about the extra signature introduced therefore it can be distributed as header files generated by the compiler. Cons: - introduces a extra signature. - slower than the current implementation of templates, there is still an indirection (a pointer dereference is not an immediate value). - the extra signature requires extra effort to keep ABI compatibility, which is an undesirable side effect, if the implementation of an algorithm changes it can't use different fields or functions without breaking the ABI. - uses more space on the stack and cpu cache. - more work for the compiler to infer the used fields and fuctions - has some issues Issues: - static method calls and normal method calls are ambigious i.e functions and delegates. - to add to that even fields get into the mess, in `a.b`, b could be a field or a method or a static field or a static method. - conditional compilation notably used in design by introspection can't be worked at runtime. but since conditional compilation is predictable unlike generic programing, maybe all possible branches could be compiled into the binary and the compiler could just link to one of them. This is just one other way to consider maybe there more, there always is, we just need to think outside the box (sometimes recursively).
Apr 11 2017