jonathanvdc / Flame

A compiler framework for managed code.
https://jonathanvdc.github.io/Flame
GNU General Public License v3.0
52 stars 4 forks source link

Design: static classes that implement interfaces #27

Closed qwertie closed 5 years ago

qwertie commented 8 years ago

Continuing from #26, It seems to me that the obvious way to support interfaces that include static members is like this:

public class Foo : IBar
{
    public static int Bar() { /* implementation */ };
    [CompilerGenerated]
    int IBar.Bar() { return Bar(); };
}

For convertibility to C#, a static class like

public static class Foo : IBar, IBaz
{
    public static int Bar() { /* implementation */ };
    public static int Baz() { /* implementation */ };
}

could be implemented either by removing static, so that a constructor and interfaces are allowed, or by doing this harder thing:

public static class Foo
{
    public static int Bar() { /* implementation */ };
    public static int Baz() { /* implementation */ };
    public static Foo_impl Instance { get { return Foo_impl.Instance; } }
}
public static class Foo_impl : IBar, IBaz
{
    int IBar.Bar() { return Foo.Bar(); };
    int IBaz.Baz() { return Foo.Baz(); };
    private Foo_impl() {}
    // thread races don't seem to matter here
    private static Foo_impl __instance__;
    internal static Foo_impl Instance => __instance__ ??= new Foo_impl();
}

I'm not sure what you meant about lexical macros, but this cannot be done with a lexical macro since the macro can't tell which methods are members of an interface.

Removing static from static class is a problem unfortunately, because C# only allows extension methods in static classes, and there's no logical reason not to allow a static class to contain both extension methods and interface implementations. On the other hand, maybe we could flip it around and move the extension methods to a new class if necessary. In fact, yes, I like it. I've often wanted to define an extension method inside a non-static class and this would be possible like so:

// Enhanced C#
public static class Foo : IBar, IBaz
{
    public static int Bar() { /* implementation */ };
    public static int Baz() { /* implementation */ };
    public static void ExtensionMethod<T>(this T o) { };
}

// Plain C# - note that the class is no longer `static`
public class Foo : IBar, IBaz
{
    public static int Bar() { /* implementation */ };
    public static int Baz() { /* implementation */ };
    public static void ExtensionMethod<T>(T o) { return Foo_ext.ExtensionMethod<T>(o); };

    private Foo() {}
    private static Foo __instance__;
    // Note: I like the name "Singleton" better than "Instance" 
    // unless the latter is already more popular
    public static Foo Instance { get { return __instance__ = __instance__ ?? new Foo(); } }
    int IBar.Bar() { return Bar(); };
    int IBaz.Baz() { return Baz(); };
}
public static class Foo_ext 
{
    public static void ExtensionMethod(this T o) { };
}

Note: I just verified that plain C# allows using static Foo where Foo is not a static class.

jonathanvdc commented 8 years ago

The current (D#) implementation actually compiles singleton classes slightly differently: all non-private singleton methods are converted to instance methods. So something like

public static class Foo : IBar, IBaz
{
    public static int Bar() { /* implementation */ };
    public static int Baz() { /* implementation */ };
    public static double F() { /* implementation */ };
}

actually gets converted to:

public class Foo : IBar, IBaz
{
    // Interface implementation
    public int Bar() { /* implementation */ };
    // Interface implementation
    public int Baz() { /* implementation */ };
    // Not an interface implementation.
    public double F() { /* implementation */ };

    private Foo() {}
    private static Foo __instance__;
    public static Foo Instance { get { return __instance__ = __instance__ ?? new Foo(); } }
}

Note that D# interprets Foo, as in Foo.Bar(), as syntactic sugar for Foo.Instance. So Foo.Bar() becomes Foo.Instance.Bar(). The advantage of this approach is that there are no "thunk" functions. This reduces code size somewhat, and, more importantly, it eliminates a hard-to-optimize layer of indirection: I don't think the JIT optimizes vtable entries to eliminate those thunks. Furthermore, accessing Foo in Foo does not require a call to Instance, as Instance corresponds to the this pointer. I suppose there are disadvantages to this approach, as well, like passing a this pointer around when not strictly necessary.

Those static extension methods do seem useful. Perhaps they could even be used to create mixin interfaces?

qwertie commented 8 years ago

Yes, I understood your explanation before about how static essentially just disappears from the function declarations. I don't think that's the right approach, though. Mainly because it's not backward compatible with C#, unless the implementation does something really weird - switching static members to non-static as soon as you implement an interface. Remember, EC# is supposed to be 99.9% backward compatible. Breaking changes should be avoided, and when present, rare and minor.

Mixin interfaces? What do you mean?

jonathanvdc commented 8 years ago

it's not backward compatible with C#

Do you have a reflection scenario in mind? If so, then I completely agree. But maybe singletons warrant a different type specifier, like Scala's object. I modified static class semantics for D#, but, in hindsight, that didn't always work out the way I wanted it to. A separate keyword would also avoid breaking changes.

Mixin interfaces? What do you mean?

Extension methods have always been most useful to me when used on interfaces. So I think it'd be great if we could write something along the lines of:

public interface IExpression
{
    /// <summary>
    /// Tests if this expression node has no side-effects. 
    /// Its children can still have side-effects, though.
    /// </summary>
    bool IsConstantNode { get; }

    /// <summary>
    /// Applies the given visitor to this expression's children.
    /// </summary>
    IExpression Accept(INodeVisitor Visitor);

    /// <summary>
    /// Tests if this expression tree has no side-effects.
    /// This means that both this expression node and its children  
    /// have no side-effects.
    /// </summary>
    public static bool IsConstant(this IExpression Expr)
    {
        // Visit the expression, make sure that all of its children are
        // also constant.
    }
}

Extension properties would also be tremendously helpful here.

qwertie commented 8 years ago

I did not, no - although that's a good point. So if you're asking how it's not backward compatible:

It looks like my original proposal was incomplete, so I edited it. Search this page for Foo_ext to find it.

About the second thing, let's call it "extension methods in interfaces". I like it. It could be implemented the same way, by adding an _ext class to hold extension methods. What I want even more is default interface implementations - but that's harder. It requires a technique to store default implementations in an assembly such that they can be consumed by other assemblies.