Formal optional interfaces
Jacob Carlborg
doob at me.com
Tue Mar 5 20:03:30 UTC 2019
The next Objective-C integration feature that will be implemented will
probably be support for "protocols" (this is what Objective-C calls
"interfaces"). Objective-C protocols supports optional methods. To call
an optional method on a Objective-C protocols you're supposed to do a
runtime check to see if the object actually implements the method.
This got me thinking if this needs to be supported as well. Regular D
interfaces do not support optional methods. I asked on Slack how people
would think about this feature and I got a reply mentioning Andrei's C++
talk "The Next Big Thing" [1]. This talk includes informal optional
interfaces.
I see a couple of problems with informal optional and non-optional
interfaces:
1. Since there is no name in the code it's difficult to talk about. With
a formal interface you can say "This type need to implement Numeric"
while with an informal interface it would be more like "This type need
to implement addition, subtraction, multiplication, division, comparison
and a bunch of more things".
2. It's difficult to document an informal interface since there's no
obvious symbol to attach the Ddoc comment to.
3. With optional informal interfaces there's the issue with misspelling
or accidentally using the wrong name. If you misspell the name your
function is not called and you don't know why. There are at least three
different places where a symbol can be misspelled:
A. When implementing the code that requires the informal optional
interface, this unlikely but can happen. Perhaps a specification says to
implement a symbol named "color", which the developer reads, five
minutes later the developer implement the symbol and names it "colour"
instead.
B. When the documentation is written.
C. When implementing the informal optional interface.
Then I was thinking if of way to have formal optional interfaces, that
would both work with integrating Objective-C protocols and a more D way
of doing this at compile time. Which also would solve the above problems.
Here's the idea I have for formal optional interfaces:
* Implement a compiler recognized UDA to indicate that a method is
optional in an interface:
interface Foo
{
void foo(); // required
@optional void bar();
}
* In addition to classes, allow structs to implement interfaces. This
would not mean that a struct can be passed to a function taking said
interface or assigned to a variable of the implemented interface. It
would be used for template constraints:
interface Foo
{
void foo();
}
struct Bar : Foo
{
void foo() { }
}
If Bar doesn't define a method named "foo" it would be a compile time error.
Use with a template:
void processFoo(T : Foo)(T foo)
{
}
processFoo(Bar()); // the type of T will be "Bar"
This works today with classes.
* Implement a compiler recognized UDA to indicate that an optional
method is being implemented instead of declaring a new method. This is
required to avoid the misspelling:
interface Foo
{
@optional void foo();
}
struct Bar : Foo
{
@implements void foo() { }
}
If "@implements" is not specified a compile time error would occur. This
is the same behavior as the "override" keyword (possibly "override"
could be used here). This would check the signatures to make sure
Foo.foo and Bar.foo matches.
* Implement a __traits to check if an optional method is implemented. It
takes a type and a method signature:
interface Foo
{
@optional void foo();
}
struct Bar : Foo
{
@implements void foo() { }
}
void processFoo(T : Foo)(T foo)
{
static if (__traits(implements, T, Foo.foo))
foo.foo();
}
The "implements" trait would check not just the name but the full
signature. It's important that the second parameter is not a string but
the actual method to avoid any misspellings:
void processFoo(T : Foo)(T foo)
{
static if (__traits(implements, T, Foo.fo))
foo.foo();
}
The above example would give a compile time error because the method
"fo" doesn't existing on the interface "Foo".
If a method is not checked with a "static if" to see if it's implemented
or not a compile time error will occur if it's not implemented. If it is
implement no error will occur. This is what we have today.
void processFoo(T : Foo)(T foo)
{
foo.foo(); // compiles successfully
}
Back to the Objective-C integration. For an Objective-C interface we can
use the same syntax:
extern (Objective-C) interface Foo
{
void foo();
@optional void bar();
}
extern (Objective-C) class Bar : Foo
{
void foo() {}
}
But when calling an optional method that is not implemented no compile
time error will occur:
void main()
{
Foo a = new Bar;
a.bar(); // compiles successfully
}
Instead a runtime exception would be thrown from the Objective-C
runtime. This is how Objective-C works. It's possible to dynamically add
methods to Objective-C classes. The correct way is to do a runtime check:
void main()
{
Foo a = new Bar;
if (a.respondsToSelector("bar"))
a.bar();
}
Thoughts?
[1] https://www.youtube.com/watch?v=tcyb1lpEHm0
--
/Jacob Carlborg
More information about the Digitalmars-d
mailing list