HaxeFoundation / haxe-evolution

Repository for maintaining proposal for changes to the Haxe programming language
111 stars 58 forks source link

Default implementations in the interfaces #54

Closed posxposy closed 5 years ago

posxposy commented 5 years ago
interface IUser {
    //Fields marked as default should be initialized here;
    default private var email:String = "";

    //Methods marked as default should be implemented here;
    default public function isEmailValid ():Bool {
        /* ... */ 
        return false;
    }

    //This method is still required to be implemented by childs
    public function doSomething ():Void;
}

class Student implements IUser {
    //This method is required by IUser
    public function doSomething ():Void {
        isValidEmail (); // this method can be used here, implementation will be injected from the interface
    }
} 

class Teacher implements IUser {
    //This method is required by IUser
    public function doSomething ():Void {  }

    //Discard default impl, and use it's own
    public function isValidEmail ():Bool {
        return false;
    }
} 

Rendered version

ncannasse commented 5 years ago

What you might want here instead is similar to Java abstract methods, that forces children classes to implement certain methods defined in their superclass.

posxposy commented 5 years ago

@ncannasse

What you might want here instead is similar to Java abstract methods, that forces children classes to implement certain methods defined in their superclass.

Well, that's a half of problem :) To be fair, I picked up default implementations from Java too. Both things are useful and flexible. Maybe it is possible to support both features? Abstract methods in classes and default implementations in interfaces. That may improve Haxe as a language and made it more rich as for me, and reduce unnecessary boilerplate code in some situations.

Aurel300 commented 5 years ago

I just wanted to add that this is already possible using macros, e.g. with tink_lang partial implementations (though implementing this manually in macros is pretty easy also).

But I think proper traits (which is essentially what this is, given that your default allows both variable and method fields) might be interesting to have without having to go into macroland.

Simn commented 5 years ago

I don't want code in interfaces.

I would be open to abstract methods Java-style though, though I didn't really think the implications through yet.

RealyUniqueName commented 5 years ago

I agree with @simn. Interfaces should stay interfaces. And the proposed feature is better to be designed and implemented as "classic" abstract classes, which I'd like to see in Haxe type system some day.

posxposy commented 5 years ago

@Simn

I would be open to abstract methods Java-style though

Well, this idea actually comes from Java too:

// A simple program to Test Interface default 
// methods in java 
interface TestInterface 
{ 
    // abstract method 
    public void square(int a); 

    // default method 
    default void show() 
    { 
      System.out.println("Default Method Executed"); 
    } 
} 

Example from here: https://www.geeksforgeeks.org/default-methods-java/

But, would be nice to see "classic" abstract classes (as @RealyUniqueName says) in Haxe instead of "default" things in interfaces. Why not :)

hughsando commented 5 years ago

I think static functions/variables would be convenient in enums and interfaces, otherwise you have to make pairs of classes, which can be annoying .

jcward commented 5 years ago

Just chiming in to say, I like interface default implementations, they're very handy. I have built a macro implementation, and it's a little slow and a little hairy - so in that respect, it'd be nice if it was a built-in feature.

In my implementation, I've (ab)used metadata, which happens to keep the implementation separated (in the metadata) out of the pure interface definition:

package;

@:autoBuild(util.DefaultImplementations.apply_defaults({
  some_var:"hello world",
  get_is_bar:
  {
    return some_var == "bar";
  },
  set_is_bar:
  {
    // Note: variable name 'input_value' is defined in the interface below
    if (input_value) some_var = "bar";
    return input_value;
  }
}))
interface IFoo
{
  public var some_var:String;

  public var is_bar(get,set):Bool;
  public function get_is_bar():Bool;
  public function set_is_bar(input_value:Bool):Bool;
}

The most hairy part is that, while it's building fields for the given class, it doesn't know which interface triggered the macro. So it scans all the interfaces of the class (and parent classes) to find the one matching the fields provided in the metadata. It needs the interface, because while the metadata provides the Expr, the interface provides the Field definition.

I say abused metadata, because technically I don't think metadata supports generic expressions per the manual... but it does work in both Haxe 3 and 4. Although vshaxe isn't always happy about it -- some if-defs have been necessary.

One downside of separating the interface definition from the implementation is that the method parameter names, e.g. input_value, is in the interface, and must be used in the implementation.

ETA: Oh, and it's sometimes handy to @:final a default method to disallow classes from providing an implementation:

  some_method: @:final {
    ...
  }
Gama11 commented 5 years ago

I don't think metadata supports generic expressions per the manual...

I think that's perfectly valid:

arguments to compile-time metadata can be any valid expression.

Simn commented 5 years ago

This has been rejected by nay votes of @ncannasse, @nadako, @RealyUniqueName and @Simn.