4

I am trying to write a generic compare function (like the c strcmp) in Haxe3 for a template type A, assuming that this template type has a less-than-or-equal-to operator "<=".

I saw in the Haxe3 documentation (http://haxe.org/manual/haxe3/features) that you can do a similar job if you want to assume that a template type has the new function:

@:generic static function foo<T:{function new(s:String):Void;}>(t:T) {
    trace(Type.typeof(t)); // TClass([class String]) / TClass([class Template])
    return new T("foo");
}

So, I tried the same technique with a "le" function:

class Main {
    @:generic static public function compare_<A:{function le(y:A):Bool;}>(x:A,y:A): Int {
        if (x.le(y) && y.le(x)) return 0;
        else if (x.le(y)) return -1;
        else return 1;
    }


    static function main() {
        var a:MyInt  = new MyInt(1);
        var b:MyInt  = new MyInt(2);
        trace(compare_(a,b));
    }
}

class MyInt {
    var data:Int;
    public function new(i:Int) {this.data = i; }
    public function le(y:MyInt){return data <= y.data;}
}

The compare_ function above works fine for any type with the prerequisite "le" function. The above code returns -1 as expected. But it is rather inconvenient, obviously, to create a new class for Int, just to provide the le function. My question is, is there a way to re-write the compare_ function to make it work for any template type (Int,Float, other types with overloaded operators) with a "<=" operator defined?

The following is what I have tried:

@:generic static public function compare_<A:{@:op(X <= Y) function le(x:A,y:A):Bool;}>(x:A,y:A): Int {
     if (x <= y && y <= x) return 0;
     else if (x <= y) return -1;
     else return 1;
 }

which of course does not compile. Haxe complains that "le" is undefined.

I am looking for lightweight solutions without involving macros, as I intend to re-use the haxe generated code in other languages without dependance on Boot. or scuts. Dynamic is also not good for my purposes as there is no type-safety at all.

Thanks in advance.


Update: I did some additional reading, and figured I might be able to inject an le method the hard way into every type I want, using the using and callback mechanism. Please see my related question on that "import and using may not appear after a type declaration" -- the haxe using magic.

Community
  • 1
  • 1
thor
  • 21,418
  • 31
  • 87
  • 173
  • Not sure if I've understood your question entirely. Is Reflect.compare(a,b) close to what you are looking for? http://api.haxe.org/Reflect.html#compare It is a `@:coreApi` which means it is implemented separately for each platform. See Javascript's implementation here: https://github.com/HaxeFoundation/haxe/blob/development/std/js/_std/Reflect.hx#L65 .... Not sure how you would make it compare using either that OR your template's "le()" function though... – Jason O'Neil Dec 21 '13 at 01:41
  • Thanks, Jason. I tried Reflect.compare(a,b). But I wanted a, b to be both from a template type T. I remember haxe compilation telling me something about only the type is only known at runtime, not compile time. Something like `function compare_(x:A,y:A):Int { return Reflect.compare(a,b); }`. – thor Dec 21 '13 at 05:32
  • I guess I wanted something that can implicitly use the "<=" operator. If not possible, then explicitly. But I can't find a way in haxe to explicitly expect/require the "<=" operator. – thor Dec 21 '13 at 05:35
  • `I am looking for lightweight solutions without involving macros, as I intend to re-use the haxe generated code in other languages without dependance on Boot. or scuts. Dynamic is also not good for my purposes as there is no type-safety at all.` There is no such solution. You keep speaking of templates when there's really just type parameters. That is an important part of the issue. If you want to have the power of templates, you will have to use macros. Or you'll have to use reflection, but that also has limitations. You'll have to remove a few constraints to allow for a solution space ;) – back2dos Dec 21 '13 at 08:16
  • @back2dos. This is probably a language issue. Haxe type parameters looks similar to C++ template types to me. So, that's what I meant by template type. What I want to do is being able to use the generated C++ code without the entire Boot mechanism. So far, C++ will not check if a type parameter has an "<=" operator defined or not, and will go ahead and compile your code with the "<=" op. It will complain if it's not defined, but keep silent otherwise. My problem is, although haxe has type safety, it doesn't have expressiveness to specify that a type has a operator defined. Just can't say it. – thor Dec 21 '13 at 09:16
  • Well, Haxe type parameters may look similar to C++ templates if you have a C++ background. But they are a lot closer to Java and C#. Rather than having two distinct Turing complete languages like C++, Haxe uses the same for programming and meta-programming. And latter, i.e. macros, has nothing to do with Boot. – back2dos Dec 21 '13 at 18:35
  • You are right. Still, it's kind of a pain that you have to go to advanced features like macros to achieve something really simple like sorting basic types without defining a trivial compare function/predicate. – thor Dec 21 '13 at 18:59

1 Answers1

7

Two type safe ways to deal with the problem:

1. Macros

class Main {
    macro static public function compare_(a, b)
        return macro @:pos(a.pos) { 
            var a = $a, 
            b = $b; 
            if (a <= b) 
                if (b <= a) 0;
                else -1;
            else 1;
        }  

    static function main() {
        var a = 1,
            b = 2;
        trace(compare_(a,b));
    }
}

2. Abstracts

abstract Comparator<T>({f: T->Int }) {
    public function new(f) 
        this = { f: f };

    public function compare(other:T):Int
        return this.f(other);

    @:from static function fromInt(i:Int)
        return simple(i);

    @:from static function fromFloat(f:Float)
        return simple(f);

    @:from static function fromString(s:String)
        return simple(s);

    @:from static function fromComparable<A>(o:{ function compareTo(other:A):Int; })
        return new Comparator(o.compareTo);

    static function simple<X>(o:X):Comparator<X>
        return new Comparator(Reflect.compare.bind(o));
}

class Main {
    static public function compare_<A>(a:Comparable<A>, b:A)
        return a.compare(b);

    static function main() {
        function comparable(value)
             return { 
                 value: value, 
                 compareTo: function(other) 
                     return Reflect.compare(value, other.value) 
             }          
        trace(compare_(1,2));
        trace(compare_(1.5,2.5));
        trace(compare_('foo','bar'));
        trace(compare_(comparable(1),comparable(2))); 
    }
}
back2dos
  • 15,588
  • 34
  • 50