Why interfaces can contain properties?

Hey,
3 questions, please :slight_smile:

  1. Why haxe allow interfaces to contain properties?
interface A {
  a: String;
}

Interfaces should be about functionality not implementation, isn’t it?

  1. why I should use typedef if an interface can include properties?

  2. The compiler behavior is a little weird, I think…

class Test {
    static function main() {
        var a:A1 = { a: '1' }; // Test.hx:3: characters 8-29 : { a : String } should be A1
        var b:A1 = cast({ a: '1' }); // This is valid
        var c:A2 = { a: '1' }; // This is valid
    }
}

interface A1 {
    var a: String;
}
typedef A2 = {
    var a: String;
}
1 Like
  1. But a property can also be functionality :slight_smile: . Especially when it comes to get/set functions. Interfaces also allow public variables. Those are also not implementation details, but promising that a class will allow you access to a certain variable :slight_smile: .

  2. Why use typedef? Well, here’s one use case I recently had. I used a typedef in order to map JSON data into a Haxe Map. The JSON has nested data.

typedef StoryDataEntry =
{
var type: String;
var id: String;
var branches: Array<BranchEntry>;
}
typedef BranchEntry =
{
	var value: String;
	var target: String;
}

and last but not least

typedef StoryDataEntries = Array<StoryDataEntry>;

And now check how beautifully Haxe simply maps my JSON into a nice handy object structure:

var entries: StoryDataEntries = Json.parse(json);

That’s it. Pronto. Good luck attaining such brevity with interfaces :smiley:

  1. Well, I think you are discovering the answers to your question regarding differences between Interfaces and typedefs :slight_smile:

Check this out:

I believe object literals are compiled into anonymous structures, and that typedefs are more tolerant towards object literal assignments.

I’m a bit sketchy on the details on this last one. I will let the more seasoned member of our community answer whatever I didn’t and/or correct me :slight_smile:

1 Like

I guess the main question boils down to the difference between interfaces and anonymous structures.

A “typedef” is just an alias: a name you give to a type.

Example:

typedef Email = String;
typedef Age = Int;
typedef Foo = IBar;

A typedef may refer to a class, interface, enum or whatever. But it’s particularly useful to give names to anonymous (= nameless) types, because there would be no other way to reference such types (and you’d have to write the same type everywhere you use it).

Haxe in fact has two kinds of anonymous types:

  1. structures: { function quack():Void; }
  2. functions: Ingredients->Cake

Most commonly, you will see typedefs used for structures.

Now: what is the difference between structures and interfaces? For determining whether a value is valid for a type or not, there are two approaches:

  1. structural subtyping: if a value implicitly matches the expected structure, it is considered valid. This is very similar to duck typing. If you say typedef Duck = { function quack():Void; } then anything with a compatible quack method is considered a duck.
  2. nominal subtyping: a value is only valid for a type if it’s an instance of a class that is explicitly derived from that type. If you say interface Duck { function quack():Void; } then only instances of classes that implement that interface are considered valid ducks. You can make the most duck-like object that ever walked the earth … so long as it’s not the instance of a class that has implements Duck in it, it will never be a duck.
    In your code you have var b:A1 = cast({ a: '1' });. That only works because it’s an unsafe cast and in fact it will throw exceptions on static platforms (cpp, java, c#, hashlink, flash). A safe cast will do so on all platforms var b = cast({ a: '1' }, A1);. An anonymous object will never be valid for an interface, because it doesn’t even belong to any class.

As for when to use what: it depends :wink:

Both implicitness and explicitness have their strengths and weaknesses. For both approaches, you will find no shortage of programmers that will tell you that you should always prefer one over the other.

Advantages of structures:

  1. They can be used with anonymous objects (and thus translate to JSON more seamlessly)

  2. Can be used to abstract over 3rd party code, e.g.

    typedef Point = {
      var x(default, null):Float;
      var y(default, null):Float;
    }
    function closest(points:Array<Point>, to:Point):Point { 
      /* exercise for the reader */
    }
    

    That function has a high chance to work with various point classes from various libraries. You can’t make 3rd party code compatible with your interfaces (that would require you to modify the classes), but you can make it compatible with your structures (by designing them accordingly).

Advantages of interfaces:

  1. If an interfaces changes, then all classes that intend to implement it get the proper errors until they adapt to the changes. In contrast, if a structure changes, you get errors in all the places where you’re trying to pass an unadapted object to the changed structure.
  2. On most of the static platforms, interfaces yield better performance than structures. AFAIK hashlink is optimized not to penalize anonymous objects and structures, but java, c#, cpp and flash will perform better with interfaces/classes.

As for whether or not interfaces should contain properties … like anything, it can be abused, but that doesn’t make it wrong per se. If you strive to follow principles like Tell, Don’t Ask, then your interfaces (and even classes) will quite naturally tend toward not having too many public fields / properties.

6 Likes

Why not? Classes are also allowed to have public variables and it can be useful, but you can not use them.

Typedef are only aliases that mean you can replace the left part with the right part, like in your example:

var c:A2 = (...)
var c:{var a:String} = (...)

would be the same thing.

var a:A1 = { a: '1' }; You are trying to assign a structure to a variable of type object (here an interface to be exact, but only classes can implement interfaces), this is normal not to work.

var b:A1 = cast({ a: '1' }); Is indeed “valid” since an unsafe cast means “trust me I know better”, but will fail at runtime the same way that var b:A1 = cast true; will be “valid” but fail.

var c:A2 = { a: '1' }; is indeed correct, you are assigning a structure to a structure type.
The equivalent for A1 would be var d:A1 = new ClassImplementingA1();.

The main takeaway is that objects and structure are not interchangeable and do not mix.

2 Likes

Thanks for you guys for the detailed, crystal clear, answers, it took me some time to read them thoroughly :slight_smile: