HaxeFoundation / code-cookbook

The Haxe Code Cookbook - A community driven resource website for learning Haxe in practise
https://code.haxe.org
112 stars 87 forks source link

trait pattern example #148

Closed ghost closed 2 years ago

nadako commented 4 years ago

I'm not sure if this is comparable with type classes/traits.

The main benefit of type classes comes from separation of values and interfaces, so you can have cheap and automatically used "adapters" (aka type class instances/trait impls) from any data type to given interface. But here it is not the case.

If Haxe had traits the example code would be:

interface HasFullName {
  function fullName():String;
}

function printFullName(o:HasFullName) {
  trace(o.fullName());
}

class Person {
  var firstName:String;
  var lastName:String;
}

// some made-up syntax, similar to abstract
trait HasFullName(Person) {
  function fullName():String {
    return this.firstName + " " + this.lastName;
  }
}

printFullName(new Person(...)); // magically works because there's a HasFullName trait for Person defined
ghost commented 4 years ago

I agree it is not quite the same concept. I just didn't know what to call the pattern. It reminded me of traits and typclasses insofar as, for both, one can attach big collections of functions to types by implementing just one or two functions for that type.

I suppose if you wanted "real" traits in Haxe, you might be able to get something like that with a macro. Perhaps a macro could create an abstract with an opaque name, and that unifies with a structural type (or typedef for convenience) which could then be used as the type constraint in implementations (like in your printFullName above). The abstract could declare implicit conversion to the structural type that is the "trait". Might work... ?

In any case, I use the above pattern but have had a hard time figuring out what to call it.

ghost commented 4 years ago

Just an Off Topic follow up to my previous comment in which I mused about a closer-to-traits concept:

Its not very nice. Even if a build macro were written to generate the abstract in the following, using it isn't the nicest expereince:


typedef HasFullName = {
    public function fullName():String;
};

class Person1 {
    public var first:String;
    public var last:String;

    public inline function new (f,l) {
        first = f;
        last = l;
    }
}

abstract HFN1(Person1) from Person1  {

    inline function new (p1) {
        this = p1;
    }

    // Not great ....
    @:to
    function toHasFullName():HasFullName {
        return { fullName: () -> fullName() };
    }    

    public function fullName():String {
        return this.first + " " + this.last;
    }

}

class Trait {

    static function printFullName(o:HasFullName) {
        trace(o.fullName());
    }

    static public function main () {
        var p1 = new Person1("bob", "jones");

        printFullName( (p1 : HFN1) );  // have to put the abstract type hint somewhere... boo!

    }

}

:P oh well!