Thread overview
Formal optional interfaces
Mar 05, 2019
Jacob Carlborg
Mar 05, 2019
jmh530
Mar 06, 2019
Paul Backus
March 05, 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
March 05, 2019
On Tuesday, 5 March 2019 at 20:03:30 UTC, Jacob Carlborg wrote:
> [snip]
>
> * 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() { }
> }
> [snip]

What about with free-standing functions?

@impl void foo(Bar x) { }
or maybe would need to do
@impl!Foo void foo(Bar x) { }

This reminds me of open methods [1]

[1] https://dlang.org/blog/2017/08/28/open-methods-from-c-to-d/
March 06, 2019
On Tuesday, 5 March 2019 at 20:03:30 UTC, Jacob Carlborg wrote:
> 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:
>
> [...]

Atila Neves's `concepts` library on dub [1] already addresses most of these issues, without requiring new language features or invasive changes to existing code.

What you propose might have been a good idea if it were part of D from the start (or if you were designing "D 3.0"), but trying to retrofit it now, after so much code (e.g., all of Phobos) has already been written using the existing "informal" style, seems like the worst of both worlds to me. It won't save us from having to deal with the shortcomings of "informal" interfaces, because code that uses them will still exist, but it *will* make the language more complicated and difficult to learn, and force programmers to waste their time dealing with incompatibilities between "formal" and "informal" code instead of solving actual problems.

[1] https://code.dlang.org/packages/concepts