Operator overloading and inheritence

Is there some way that I can have two types, say G and P where G is a subtype of P and both have operator overloading?

Here is what I’ve tried that almost works.

I have two interfaces: GuardedProcessI extends ProcessI. I’m happy with them except that I would like to use operator overloading. So I built GuardedProcess as an abstract type on top of GuardedProcessI and Process on top of ProcessI. There are from and to conversions for both, so most of the time I can ignore the interfaces and just use the abstract types. (Or perhaps the other way around.)

The problem is that that GuardedProcess is not a subtype of Process.

Here is a small example.

package tbc;

interface ProcessI {
    public function bind( f : Int -> ProcessI ) : ProcessI ;
}

abstract Process( ProcessI ) to ProcessI from ProcessI {

    public inline function new(p : ProcessI) {
        this = p;
    }

    /** See ProcessI.bind */
    @:op( A >= B )
    public inline function bind( f : Int -> ProcessI ) : Process {
        return new Process( (this:ProcessI).bind( f ) ) ;
    }
}

interface GuardedProcessI extends ProcessI {
    public function bind( f : Int -> ProcessI ) : GuardedProcessI ;
}

abstract GuardedProcess( GuardedProcessI )
to GuardedProcessI from GuardedProcessI
{

    public inline function new(gp : GuardedProcessI) {
        this = gp;
    }

    /** See GuardedProcessI.bind */
    @:op( A >= B )
    public inline function bind( f : Int -> ProcessI ) : GuardedProcess {
        return new GuardedProcess( (this:GuardedProcessI).bind( f ) ) ;
    }
}

@:expose class MFE {
    public static function one( x : Process, f : Int -> Process ) : Process {
        return x.bind( f ).bind(f) ;
    }

    public static function two( x : Process, f : Int -> Process ) : Process {
        return (x >= f) >= f ;
    }

    public static function twoPoint5( x : Process, f : Int -> Process ) : Process {
        return x.bind( f ) >= f ;
    }

    public static function three( x : GuardedProcess, f : Int -> Process ) : GuardedProcess {
        return x.bind( f ).bind(f) ;
    }

    public static function four( x : GuardedProcess, f : Int -> Process ) : GuardedProcess {
        return x.bind( f ) >= f ;
    }

    public static function five( x : GuardedProcess, f : Int -> Process ) : Process {
        return one( x, f) ;   // Error: tbc.GuardedProcess should be tbc.Process For function argument 'x'
    }

    public static function six( x : GuardedProcess, f : Int -> Process ) : Process {
        return two( x, f) ;  // Error: tbc.GuardedProcess should be tbc.Process For function argument 'x'
    }


    public static function seven( x : ProcessI, f : Int -> ProcessI ) : ProcessI {
        return (x >= f) >= f ;  // Cannot compare tbc.ProcessI and Int -> tbc.ProcessI
    }

    public static function eight( x : GuardedProcessI, f : Int -> ProcessI ) : ProcessI {
        return seven( x, f ) ;  
    }
}

Everything compiles fine with version 4.0.5 except for functions five, six, and seven.

  • I can fix function five by changing the parameter type of function one to ProcessI or by changing the call to one( (x:ProcessI), f) or to one( (x:GuardedProcessI), f), but I don’t want to do either; I really just want the client code (like the MFE class) to only have to deal with two types.
  • I can fix function six by rewriting the call to two( (x:ProcessI), f) or to two( (x:GuardedProcessI), f), but again this means that the client coder needs to know about both the abstract types and interface types.
  • Functions seven and eight show what happens if I only use the interface types in the client code. The >=s in seven aren’t recognized as overloads because there is no implicit conversion from ProcessI to Process. I can put the conversion in by changing (x >= f) to (new Process(x) >= f). Once again this requires the client coder to know about both the interfaces and the abstracts.

Maybe I should clarify that my only motivation for using abstract types is for overloading. If overloading could be applied to methods of interfaces and classes, then there would be no issue.

Because GuardedProcess extends Process, you can add to Process to GuardedProcess :), live example: Try Haxe !

Not sure I understand seven and eight, if you use the interfaces without the abstract you don’t have overloads so the errors are expected and you need to explicitly use the abstract versions

If you don’t want the interfaces to be visible outside you can make them private interface

1 Like

Wonderful. Why didn’t I think of that!

seven and eight The idea was that the client code should only be written in terms of either

(a) Process and GuardedProcess or

(b) ProcessI and GuardedProcessI

seven and eight were just to show what goes wrong if I do (b). Since (a) is possible, there is no need to find a way to make (b) work.

Thanks.

1 Like