Reputation: 184
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
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
Reputation: 19026
I'm basically trying to make a function that will accept a variable of type
String
,Int
,Float
, orBool
...
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
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:
@from
and @to
instead of @:from
and @:to
, which means your conversion methods won't have any effect.@: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