0x.dummyVar
0x.dummyVar

Reputation: 184

Haxe: Using abstracts to define groups of types

I've found out about abstracts in Haxe, and I'm trying to make a sample project to learn how to use them.

So far I've read through: https://haxe.org/manual/types-abstract.html, https://haxe.org/blog/abstracting-primitives/, http://old.haxe.org/manual/abstracts, and https://code.haxe.org/category/other/passing-different-types-to-a-function-parameter.html. If anyone has other material they can direct me to, I would appreciate it.

I'm basically trying to make a function that will accept a variable of type String, Int, Float, or Bool, and traces it. I've gotten a bit stuck though, when I try to compile with: haxe -main Main -cpp Export I get the error: abstracts/Comparison.hx:99: characters 42-43 : Type not found : A. I'm trying to find out why this is happening and how to fix it.

I feel like I'm attaching too much code, but this is what I've written so far:

Main.hx

import abstracts.*;

class Main
{
    public static function main()
    {
        var toPrint:Array<String> = ["String1", "String2", "String3"];
        printArray(toPrint);
    }

    public static function printArray(toPrint:PrintableArray)
    {
        for (element in toPrint)
        {
            trace(element);
        }
    }
}

abstracs/Comparison.hx

package abstracts;

enum Either<A, B>
{
    Left(v:A);
    Right(v:B);
}

abstract Of2<A, B>(Either<A, B>) from Either<A, B> to Either<A, B>
{
    @from inline static function fromA<A, B>(a:A):Of2<A, B>
    {
        return Left(a);
    }
    @from inline static function fromB<A, B>(b:B):Of2<A, B>
    {
        return Right(b);
    }

    @to inline static function toA():Null<A>
    {
        return switch (this)
        {
            case Left(a): a;
            default: null;
        }
    }
    @to inline static function toB():Null<B>
    {
        return switch (this)
        {
            case Right(b): b;
            default: null;
        }
    }
}

abstract Of3<A, B, C>(Either<Either<A, B>, C>) from Either<Either<A, B>, C> to Either<Either<A, B>, C>
{
    @from inline static function fromA<A, B, C>(a:A):Of3<A, B, C>
    {
        return Left(Left(a));
    }
    @from inline static function fromB<A, B, C>(b:B):Of3<A, B, C>
    {
        return Left(Right(b));
    }
    @from inline static function fromC<A, B, C>(c:C):Of3<A, B, C>
    {
        return Right(c);
    }

    @to inline static function toA():Null<A>
    {
        return switch (this)
        {
            case Left(Left(a)): a;
            default: null;
        }
    }
    @to inline static function toB():Null<B>
    {
        return switch (this)
        {
            case Left(Right(b)): b;
            default: null;
        }
    }
    @to inline static function toC():Null<C>
    {
        return switch (this)
        {
            case Right(c): c;
            default: null;
        }
    }
}

abstract Of4<A, B, C, D>(Either<Either<A, B>, Either<C, D>>) from Either<Either<A, B>, Either<C, D>> to Either<Either<A, B>, Either<C, D>>
{
    @from inline static function fromA<A, B, C, D>(a:A):Of4<A, B, C, D>
    {
        return Left(Left(a));
    }
    @from inline static function fromB<A, B, C, D>(b:B):Of4<A, B, C, D>
    {
        return Left(Right(b));
    }
    @from inline static function fromC<A, B, C, D>(c:C):Of4<A, B, C, D>
    {
        return Right(Left(c));
    }
    @from inline static function fromD<A, B, C, D>(d:D):Of4<A, B, C, D>
    {
        return Right(Right(d));
    }

    @to inline static function toA():Null<A>
    {
        return switch (this)
        {
            case Left(Left(a)): a;
            default: null;
        }
    }
    @to inline static function toB():Null<B>
    {
        return switch (this)
        {
            case Left(Right(b)): b;
            default: null;
        }
    }
    @to inline static function toC():Null<C>
    {
        return switch (this)
        {
            case Right(Left(c)): c;
            default: null;
        }
    }
    @to inline static function toD():Null<D>
    {
        return switch (this)
        {
            case Right(Right(d)): d;
            default: null;
        }
    }
}

abstracts/Printable.hx

package abstracts;

abstract Printable(Comparison.Of4<String, Int, Float, Bool>) {}

abstracts/PrintableArray.hx

package abstracts;

abstract PrintableArray(Array<Printable.Printable>) {}

Much appreciated!

Upvotes: 2

Views: 562

Answers (3)

0x.dummyVar
0x.dummyVar

Reputation: 184

I've found something that works with a bit of runtime overhead:

using Type;

class Multimorph {

    public static inline function value ( value : AnyAny ) {
        return value.enumParameters () [ 0 ];
    }

    public static inline function position ( value : AnyAny ) {
        return value.enumIndex ();
    }

}

typedef Any2 = Of2<Dynamic, Dynamic>;
typedef Any3 = Of3<Dynamic, Dynamic, Dynamic>;

enum About2 <A, B> {
    PosA ( value : A );
    PosB ( value : B );
}
enum About3 <A, B, C> {
    PosA ( value : A );
    PosB ( value : B );
    PosC ( value : C );
}

abstract Of2 <A, B> ( About2<A, B> ) from About2<A, B> to About2<A, B> {
    @:from static inline function from_A <A, B> ( val : A ) : Of2<A, B> return About2.PosA ( val );
    @:from static inline function from_B <A, B> ( val : B ) : Of2<A, B> return About2.PosB ( val );
    @:to inline function to_A () : Null<A> return switch ( this ) { case About2.PosA ( val ): val; default: null; }
    @:to inline function to_B () : Null<B> return switch ( this ) { case About2.PosB ( val ): val; default: null; }
}
abstract Of3 <A, B, C> ( About3<A, B, C> ) from About3<A, B, C> to About3<A, B, C> {
    @:from static inline function from_A <A, B, C> ( val : A ) : Of3<A, B, C> return About3.PosA ( val );
    @:from static inline function from_B <A, B, C> ( val : B ) : Of3<A, B, C> return About3.PosB ( val );
    @:from static inline function from_C <A, B, C> ( val : C ) : Of3<A, B, C> return About3.PosC ( val );
    @:to inline function to_A () : Null<A> return switch ( this ) { case About3.PosA ( val ): val; default: null; }
    @:to inline function to_B () : Null<B> return switch ( this ) { case About3.PosB ( val ): val; default: null; }
    @:to inline function to_C () : Null<C> return switch ( this ) { case About3.PosC ( val ): val; default: null; }
}

Seems to work pretty well, but you have to call Multimorph.value ( v : AnyAny ) to get the literal value. It can also give you the index of the type of the value, and is pretty readable with a static extension:

using Multimorph;

class Main {

    static function main () {
        trace ( func ( "Hello World!") );
    }

    static function func ( arg : Of2<Int, String> ) {
        if ( arg.position () == 0 ) {
            return 'Was Passed an Integer `${ arg.value () }`';
        } else {
            return 'Was Passed a String `${ arg.value ()}`';
        }
    }

}

Here's a script that will make larger Multimorph packages.

Upvotes: 0

Jeff Ward
Jeff Ward

Reputation: 19026

I'm basically trying to make a function that will accept a variable of type String, Int, Float, or Bool ...

If this is the core of the issue, you might also look into my overload library. It is a macro that allows function overloading in Haxe. It decides at compile-time, based on the types of the inputs, which of the functions to call. It has some advantages over haxe.ds.Either (aka Left / Right), namely it's a compile-time feature with no runtime overhead. And some disadvantages: functions cannot easily be invoked at runtime, or with Dynamic inputs, types must be known at compile time.

Upvotes: 0

Gama11
Gama11

Reputation: 34138

The Type not found : A error is caused by trying to access the type parameter of a type instance in a static function. Static functions only have access to type parameters declared on the function itself. @:to methods don't have to be static, so you can simply remove the keyword.

There are more problems though:

  • You used @from and @to instead of @:from and @:to, which means your conversion methods won't have any effect.
  • Even when changed to @:from, the compiler won't know what to do with fromA / fromB / fromC / fromD since they all look the same to him. The type parameters are declared on the static methods, so there's no concrete String / Int / etc type on which a selection could be based. You might need concrete fromString / fromInt / etc functions.
  • Abstract implicit casts are not transitive (see example at the bottom here). This means that your Printable abstract allows no implicit conversions at all. It could be a simple typedef instead:

    typedef Printable = Comparison.Of4<String, Int, Bool, Float>;
    
  • Similarly, you can't iterate over a PrintableArray without the iterator() method being forwarded, for instance with @:forward(iterator). That might lead issues with variance like here though. You're also missing an implicit conversion from Array<Printable> - in short, a typedef would be simpler again.

  • Even if all of the above is addressed, your trace() won't have the desired output. The implicit @:to casts aren't applied here since they're a compile time feature, and trace() accepts Dynamic. You would end up with something like this:

    source/Main.hx:15: Left(Left(String1))
    

    You would have to add a toString() method to your abstract to avoid printing the wrapper enums:

    function toString():String {
        return Std.string(switch this {
            case Left(Left(v)): v;
            case Left(Right(v)): v;
            case Right(Left(v)): v;
            case Right(Right(v)): v;
        });
    }
    

Upvotes: 1

Related Questions