www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Interface Limitations in D

reply Elmar <chrehme gmx.de> writes:
Hello D community.

This post is about `interface` design in D.

First, I see good additions compared to what I know from other 
languages, mostly Java. Interfaces can be used to share function 
implementations across multiple classes. It's very great that D 
`interface`s can have pre- and post-conditions. This solves the 
classic readability/comprehensibility problem of interfaces and 
helps to implement and use interfaces in the intended way instead 
of misusing interfaces in non-intended ways.

But I also see a few limitations compared with Java. This is not 
a feature request. I'd only like to know what you think about 
these 5 points or if you see design-reasons:

* **`interface`s don't permit runtime constants.**

       Not limiting, only weird. Constants are stronly-pure 
niladic (no-arguments) functions and D syntactically doesn't 
differentiate between zero-argument function calls and variable 
accesses. I could define a static niladic function with constant 
return value in an `interface`. But the constant-member-syntax
       ```D
       static immutable myVar = 0;
       ```
       is easier to write.

       Constants are no state. The purpose of an interface is to 
describe API which is not supposed to change at runtime or during 
software evolution while not fixing behaviour and ideally not 
fixing future additions.

* **`interface`s do not permit overriding defaults**

       D's interfaces don't allow default implementations of 
static and non-static functions unlike Java. This makes class 
implementations more verbose and promotes code duplication 
because you need to repeat the default implementation in every 
implementing class, even if you don't care.
       Java uses the `default` keyword as attribute to allow for 
overriding a default-implemented interface method.

       Default methods are most important for the ability to 
extend interfaces without requiring old classes to implement 
newly added methods and without introducing stateful behaviour.
       A partial workaround for the extensibility problem is the 
definition of a new interface which also contains the old one.

       If constant declaration syntax (as previously mentioned) 
would be added to `interface`s, then runtime constants with 
default value or without any value could be polymorphically 
overridable within `class`es (overridden with a different 
constant value). When they are overridable (e.g. using the 
`default` keyword, opposite to `abstract`), they could be 
syntactic sugar for niladic functions which return a constant 
value and **optionally** could do a one-time-computation of the 
return value when the static-constant-function is called the 
first time.

       Abstract classes are no replacement because, first, you 
cannot inherit multiple abstract classes and, second, abstract 
classes implement partial incomplete behaviour while interfaces 
don't implement behaviour.

* **Non-polymorphic inheritance exists (`alias this` in `struct`s 
or `class`es) but no non-polymorphic `interface`s for structs**

       This one is most meaningful.

       In my current project in D, I'm working on a low or 
medium-low level and it's not suitable to use classes (they also 
need to work in Better-C). I don't need polymorphy. I only like 
to guarantee a **consistent** interface among my `struct`s. It 
makes life of users easier and prohibits others from "inheriting" 
my struct properties in unintended ways.

       The current way of creating non-polymorphic `interface`s is 
cumbersome: create a `mixin template` which mainly instantiates a 
selfmade trait-template (a predicate) on `this` to check that the 
environment implements certain function signatures. If I want to 
use a non-polymorphic `interface` as a type, I'm using type 
parameters and I use the trait-predicate in the `if`-constraint 
of the templated entity.

       It's more difficult to read and it's more verbose.

       ---

       Sure, one could avoid explicit template notaton. A solution 
would be a `static interface` (compile-time-dynamically 
dispatched methods and constants) which represents a uniform 
function-layout and a traits-predicate that is generated from the 
`static interface` (or is at least simulated and implicitly 
accessible via `is(type : interfaceName)`). Any implementing 
types must satisfy this predicate.
       Static `interface`s behave this way:

       - When used as variable type, `static interfaces` would 
behave like `auto` and a compile-time assertion of the associated 
predicate. Any value can be assigned to such a variable as long 
as it satisfies the predicate of the `static interface`. 
Covariant types don't need to implement the `static interface` 
explicitly to satisfy the predicate.

       - When used as a function-parameter-type or member-type it 
would be lowered to an implicit template parameter which is 
checked against the generated predicate.

       A lot of manual traits-checks using `static assert`s and 
`if`-constraints could be simplified into just a typename, e.g. 
when using Ranges.

       ---

       A simpler variant would be a `mixin interface` (purely 
static dispatch of methods) which only defines required constants 
and functions to implement for a `class` or `struct` but which 
cannot be used as a type otherwise except if there is a (default) 
implementation of all functions. (No templates are created by 
using the type.) `typeof(this)` would be allowed in `mixin 
interface`. This essentially behaves like a `mixin template` 
enhanced with "abstract" functions that must be implemented by 
the implementing `class` or `struct`.

* **`interface`s can contain type definitions/declarations and 
`alias` and can be overloaded, even though it's not documented.**

       Runtime-constants are not permitted but surprisingly 
compile-time constants are and `enum`-blocks and type 
definitions. They even can be overloaded but *without polymorphy* 
(dynamic dispatch). It seems this is not documented?

       But you should be cautious what to document:

       `alias`es can be useful if you'd want to change a function 
name without breaking old code. Probably there is a way to 
deprecate `alias`es with an annotation even.

       My personal experience however is, that `alias` 
declarations in `interface`s can be easily abused by using them 
throughout the entire code of a class, unrelated to the interface 
functions, which makes it very hard to find the declaration 
manually.

       Currently, one cannot do a lot against bad `alias`es in 
`interface`s because limiting the scope or use of 
`alias`-definitions in `interfaces` would be breaking old code. 
But since it's undocumented, worst case breakage is reduced 
(nobody said it's supported anyways, right?).


       BTW, I see benefits in enabling the *pimpl pattern* within 
interfaces.

       ```D
       interface DoodadObtainable {
           class Doodad;    // opaque type of the pimpl pattern
           Doodad obtainDoodad();
           void releaseDoodad(Doodad);
       }

       class GadgetGizmo : DoodadObtainable
       {
           class Doodad
           {
               int foo;
               this(int f) { this.foo = f; }
           }

           Doodad experienceCounter;

           Doodad obtainDoodad()
           {
               return experienceCounter = new Doodad(13);
           }
           void doDoodadThings()
           in (experienceCounter)
           {
               experienceCounter.foo++;
           }
           void releaseDoodad(Doodad d)
           {
               d.destroy();
           }
       }
       ```

       An interface-implementation would then implement the 
declared opaque type from the interface. The opaque type is 
overriden polymorphically and internally represented by a generic 
type like `void*` for classes or `void[]...` for structs. Since 
the type-size is unknown, it wouldn't allow opaque value-types as 
direct function-parameters, e.g. mere `struct`s; except when they 
are treated like variadic arguments plus an implicit byte size 
parameter for copying the data.

       A default implementation of an opaque type declarations is 
imaginable (with `default`) but without default implementation 
(or: without an abstract constructor method in the `Doodad` class 
example), opaque types cannot be instantiated outside of the 
implementation class.

       This pimpl pattern is a nice way to avoid template code 
bloat and to avoid recompilation when something changes in opaque 
types.

* **`interface`s can contain classes which violate the concept of 
an interface.**

       IMO, this is rather a limitation in persuing the purpose of 
an `interface`. It can contain a `class` whose behaviour 
specification defies the purpose of an `interface`. For Java it's 
the same. If `interface` methods use specific classes, they also 
can be defined outside of the interface, right? If you need 
cohesion put them into the same file and make them private. I 
usually use `template`s for controlling visibility and cohesion 
and to avoid ugly nestings of definitions:

       ```D
       template MyInterface() {
           class MyParameter {
               int something;
           }
           interface MyInterface {
               interface MyInterfaceClass {  // body behaves like 
an interface itself
                   void tell(string);
                   string listen();
               }
               MyInterfaceClass do(MyParameter something);
           }
       }
       ```

       *Just very annoying: you'd have to write `MyInterface!()` 
everytime. It would be very useful, if you could omit the `!()` 
if you'd like to pass zero compile-time arguments to it.*

       And you can use `import` statements in `interface`s, if 
it's located in another file.

       For me it seems, classes or structs should not be in an 
`interface` except when it is a default implementation that can 
be overridden **polymorphically**.

       Making rules more strict is not something, anyone would be 
able to change without potentially breaking old code.
Sep 19 2021
next sibling parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Sunday, 19 September 2021 at 20:00:11 UTC, Elmar wrote:
 * **`interface`s do not permit overriding defaults**

       D's interfaces don't allow default implementations of 
 static and non-static functions unlike Java. This makes class 
 implementations more verbose and promotes code duplication 
 because you need to repeat the default implementation in every 
 implementing class, even if you don't care.
       Java uses the `default` keyword as attribute to allow for 
 overriding a default-implemented interface method.

       Default methods are most important for the ability to 
 extend interfaces without requiring old classes to implement 
 newly added methods and without introducing stateful behaviour.
       A partial workaround for the extensibility problem is the 
 definition of a new interface which also contains the old one.

       If constant declaration syntax (as previously mentioned) 
 would be added to `interface`s, then runtime constants with 
 default value or without any value could be polymorphically 
 overridable within `class`es (overridden with a different 
 constant value). When they are overridable (e.g. using the 
 `default` keyword, opposite to `abstract`), they could be 
 syntactic sugar for niladic functions which return a constant 
 value and **optionally** could do a one-time-computation of the 
 return value when the static-constant-function is called the 
 first time.

       Abstract classes are no replacement because, first, you 
 cannot inherit multiple abstract classes and, second, abstract 
 classes implement partial incomplete behaviour while interfaces 
 don't implement behaviour.
You can use mixin template that will contain default implementation of an interface. You can also declare final functions in interfaces, which can partially serve for first case mentioned. Best regards, Alexandru
Sep 20 2021
parent reply Elmar <chrehme gmx.de> writes:
On Monday, 20 September 2021 at 08:32:46 UTC, Alexandru Ermicioi 
wrote:
 On Sunday, 19 September 2021 at 20:00:11 UTC, Elmar wrote:
 * **`interface`s do not permit overriding defaults**

   D's interfaces don't allow default implementations of static 
 and non-static functions unlike Java. This makes class 
 implementations more verbose and promotes code duplication 
 because you need to repeat the default implementation in every 
 implementing class, even if you don't care.
 
   ...
 
   Abstract classes are no replacement because, first, you 
 cannot inherit multiple abstract classes and, second, abstract 
 classes implement partial incomplete behaviour while 
 interfaces don't implement behaviour.
You can use mixin template that will contain default implementation of an interface. You can also declare final functions in interfaces, which can partially serve for first case mentioned. Best regards, Alexandru
Thanks, Alexandru. Final functions won't do it however, if you'd like to override the given implementation in classes. --- I want to add: after writing the post I found phobos-functions `autoImplement`, `blackHole` and `whiteHole` in `std.typecons` which automatically implement interface methods in some way. Providing default implementation support seems not more difficult: ```D mixin Interface!("MyInterface", q{ static immutable PI = 3.141592f; default static immutable CONSTANT = 0.0f; float toImplement(string s); default float isDefaulted() { return (CONSTANT ^^ PI) % toImplement(r"tau")); } }); ``` Providing default method implementations without code duplication and too much verbosity could work by defining the default method implementations as **compile-time token strings**. The above would generate ```D interface MyInterface { final static immutable PI() { return 3.141592f; } static immutable CONSTANT(); enum DEFAULT_CONSTANT = q{{ return 0.0f; }}; float toImplement(string s); float isDefaulted(); enum DEFAULT_isDefaulted = q{{ return (CONSTANT ^^ PI) % toImplement(r"tau")); }}; } ``` Then people can either `mixin (DEFAULT_foo)` explicitly (in the class implementation) or use something like `mixin DefaultImplement!MyInterface;` to automatically implement missing DEFAULT_... token strings. The solution is artificial but a limited simplistic implementation could use (compile-time) regex for this. RegEx: I don't know, whether D's engine would support matching balanced braces but my feeling assumes no. And the documentation description confirms myself. Simplicity over expressivity makes it harder to do the job with Regex and which might be disappointing for programmers from the scripting realm. I'd find a PCRE engine more practical. Worst-Case Performance does not matter if you know how to avoid the worst-case.
Sep 20 2021
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Monday, 20 September 2021 at 13:24:50 UTC, Elmar wrote:
 Providing default implementation support seems not more 
 difficult:

 ```D
 mixin Interface!("MyInterface",
 q{
     static immutable PI = 3.141592f;
     default static immutable CONSTANT = 0.0f;

     float toImplement(string s);

     default float isDefaulted()
     {
         return (CONSTANT ^^ PI) % toImplement(r"tau"));
     }
 });
 ```

 Providing default method implementations without code 
 duplication and too much verbosity could work by defining the 
 default method implementations as **compile-time token 
 strings**. The above would generate
You can actually try use mixin templates, not just mixin strings. See https://dlang.org/spec/template-mixin.html . It has nicer syntax although not as powerful as mixin strings. You can also mix in the template into a dedicated scope inside implementor of interface: ``` mixin MyDefaultImpl!() myScope; ``` I didn't try it, but you may be able to mimic default methods using this feature, in conjunction with alias this expression, although I'm pretty skeptic about this due to mangled name generation (i.e haven't tried it). So in your class you may try and explore whether this works: ``` mixin MyDefaultImpl!() def; alias this def; ```
Sep 20 2021
parent Elmar <chrehme gmx.de> writes:
On Monday, 20 September 2021 at 18:08:03 UTC, Alexandru Ermicioi 
wrote:
 On Monday, 20 September 2021 at 13:24:50 UTC, Elmar wrote:
 Providing default implementation support seems not more 
 difficult:

 ```D
 mixin Interface!("MyInterface",
 q{
     static immutable PI = 3.141592f;
     default static immutable CONSTANT = 0.0f;

     float toImplement(string s);

     default float isDefaulted()
     {
         return (CONSTANT ^^ PI) % toImplement(r"tau"));
     }
 });
 ```

 Providing default method implementations without code 
 duplication and too much verbosity could work by defining the 
 default method implementations as **compile-time token 
 strings**. The above would generate
You can actually try use mixin templates, not just mixin strings. See https://dlang.org/spec/template-mixin.html . It has nicer syntax although not as powerful as mixin strings. You can also mix in the template into a dedicated scope inside implementor of interface: ``` mixin MyDefaultImpl!() myScope; ```
Yeah, using `mixin templates` is exactly the point of the code snippet you have quoted, but together with string `mixin`s it allows one to mimic syntax features. I do collect some language features I'd like to see in D and if I ever should have enough time, I'd prototype them with string mixins for others to try them.
 I didn't try it, but you may be able to mimic default methods 
 using this feature, in conjunction with alias this expression, 
 although I'm pretty skeptic about this due to mangled name 
 generation (i.e haven't tried it).
 
 So in your class you may try and explore whether this works:

 ```
 mixin MyDefaultImpl!() def;

 alias this def;
 ```
I've been using `alias this` in `mixin templates` recently. According to Online D Editor, it works.
Sep 27 2021
prev sibling next sibling parent reply Ogi <ogion.art gmail.com> writes:
On Sunday, 19 September 2021 at 20:00:11 UTC, Elmar wrote:
 * **Non-polymorphic inheritance exists (`alias this` in 
 `struct`s or `class`es) but no non-polymorphic `interface`s for 
 structs**

       This one is most meaningful.

       In my current project in D, I'm working on a low or 
 medium-low level and it's not suitable to use classes (they 
 also need to work in Better-C). I don't need polymorphy. I only 
 like to guarantee a **consistent** interface among my 
 `struct`s. It makes life of users easier and prohibits others 
 from "inheriting" my struct properties in unintended ways.
Sounds like what [concepts](https://code.dlang.org/packages/concepts) package does. Unfortunately, doesn’t work with BetterC.
Sep 20 2021
next sibling parent Elmar <chrehme gmx.de> writes:
On Monday, 20 September 2021 at 11:45:06 UTC, Ogi wrote:
 On Sunday, 19 September 2021 at 20:00:11 UTC, Elmar wrote:
 * **Non-polymorphic inheritance exists (`alias this` in 
 `struct`s or `class`es) but no non-polymorphic `interface`s 
 for structs**

    This one is most meaningful.

    In my current project in D, I'm working on a low or 
 medium-low level and it's not suitable to use classes (they 
 also need to work in Better-C). I don't need polymorphy. I 
 only like to guarantee a **consistent** interface among my 
 `struct`s. It makes life of users easier and prohibits others 
 from "inheriting" my struct properties in unintended ways.
Sounds like what [concepts](https://code.dlang.org/packages/concepts) package does. Unfortunately, doesn’t work with BetterC.
Nice suggestion! ` implements` looks like solving most parts of my current problem. But of course, BetterC is a key feature of D and not supporting it (even the standard library) reduces the usability of BetterC. Although they provide most of the functionality, they don't provide the syntax for it which I find important, more important than compiler message goals stated by the "Concept" package. When I first saw signatures for ranges, I found the long-ish `if`-constraints unpleasantly verbose for something which really should look just like: ```D size_t count(InputRange r) { } ``` instead of ```D size_t count(Range)(Range r) if (isInputRange!Range) { } ``` But of course, the main point is the functionality: being able to constraint struct implementations like with an interface for classes. The "Concept" package makes this easier (even though the predicate is still manually defined there). Even more pleasant would be a type which is treated as `interface` when applicable and fallbacks to a template otherwise which statically asserts certain members. It would optimize away a lot of template bloat in those cases where it's not required, like with class-types. ```D bool createWith(out MyObject obj, Allocator alloc) { } ``` The `GCAllocator` doesn't implement an interface but some can implement `IAllocator`. It would be useful to fallback to a template for types which provide basic Allocator methods but don't implement `IAllocator`. Do only I think, that interface-like type names for some trait-predicates could improve the readability a lot? One could get rid of repetitively defining these predicates manually.
Sep 20 2021
prev sibling parent Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Monday, 20 September 2021 at 11:45:06 UTC, Ogi wrote:

 Sounds like what 
 [concepts](https://code.dlang.org/packages/concepts) package 
 does. Unfortunately, doesn’t work with BetterC.
What about compile time overhead? Is it lightweight on compilation time and memory? Thanks, Alexandru.
Sep 20 2021
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/19/21 4:00 PM, Elmar wrote:
 * **`interface`s don't permit runtime constants.**
 
        Not limiting, only weird. Constants are stronly-pure niladic 
 (no-arguments) functions and D syntactically doesn't differentiate 
 between zero-argument function calls and variable accesses. I could 
 define a static niladic function with constant return value in an 
 `interface`. But the constant-member-syntax
        ```D
        static immutable myVar = 0;
        ```
        is easier to write.
 
        Constants are no state. The purpose of an interface is to 
 describe API which is not supposed to change at runtime or during 
 software evolution while not fixing behaviour and ideally not fixing 
 future additions.
Not sure what you are saying here. this code is valid: ```d interface I { static immutable myVar = 0; } ``` -Steve
Sep 20 2021
parent reply Elmar <chrehme gmx.de> writes:
On Monday, 20 September 2021 at 13:35:35 UTC, Steven 
Schveighoffer wrote:
 On 9/19/21 4:00 PM, Elmar wrote:
 [...]
Not sure what you are saying here. this code is valid: ```d interface I { static immutable myVar = 0; } ``` -Steve
Wow :-D ! This makes me laugh about my own dumbness. `static` members in `interface`s work. Is there a way how I can help with documentation?
Sep 20 2021
parent bachmeier <no spam.net> writes:
On Monday, 20 September 2021 at 15:41:53 UTC, Elmar wrote:


 Wow :-D ! This makes me laugh about my own dumbness. `static` 
 members in `interface`s work.

 Is there a way how I can help with documentation?
There's an "Improve this page" link in the top right of every documentation page if it's a smaller change. For extensive changes, you can fork the repo and make a pull request with your proposed changes: https://github.com/dlang/dlang.org
Sep 20 2021