www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - replace switch for mapping

reply EntangledQuanta <EQ universe.com> writes:
Generally one has to use a switch to map dynamic components. 
Given a set X and Y one can form a switch to map X to Y:

switch(X)
{
    case x1 : y1; break;
    ....
    case x1 : y1;
}

Is there any easier way to do this where one simply specifies the 
set's rather than having to create a switch directly?

In my specific case, I have to map a two sets of types

A = {Ta1,...,Tan}
B = {Tb1,...,Tbm}

to a template function that takes two types

foo(Tak, Taj)

so, given an arbitrary (a,b) in AxB, it it maps to foo(F(a),G(b)).

Using switches would require n*m cases.

What I actually have is something like

enum X
{
    Float,
    Int,
    `Etc`
}

and X x, y;

and need to call foo!(x,y) but with x and y replaced by their 
correct D equivalent types.

e.g., if x = X.Float; y = X.Int; then I need to call 
foo!(float,int) rather than foo!(X.Float,x.Int).


This allows me to create a dynamic dispatcher at runtime and use 
a templated function rather than having to handle each type 
independently. One templated function rather than nxm regular 
functions for each type or a nxm switch.

Unfortunately, a complicating factor is that the enum's names do 
not directly correspond to the D types through some simple 
transformation(e.g., lowerCase). D doesn't seem to support 
attributes on enum members for some inane reason and using 
strings will complicate 
things[https://forum.dlang.org/post/nmgloo$bd1$1 digitalmars.com]. I think I
could use a struct though to solve that.

So, given something like

struct A
{
     ("float") enum Float = 0,
     ("int") enum Int = 1,
}

struct B
{
     ("double") enum Double = 0,
     ("short") enum Short = 1,
}

foo(T1,T2)();

create a mapping that takes an A and B and maps AxB to foo that 
does something like the following internally.

fooDispatch(A a, B b)
{
     switch(a) // Actually needs to be over attributes
     {
         case "float" :
             switch(b) // Actually needs to be over attributes
             {
                case "double" : return foo!(float, double)();
             }
         ...
     }
}


or whatever. I could write a string mixin that generates the 
above code but I'm hoping I don't have to and some genius will 
find a simple way to do it quickly, efficiently, and performant.
Aug 31 2017
next sibling parent EntangledQuanta <EQ universe.com> writes:
I came up with a library solution that isn't pretty ;/

I offer it up to the gods, but being gods, they probably don't 
care.


template EnumMapper(alias func, string[] args, eT...)
{
	import std.meta, std.typecons, std.traits, std.string, 
std.algorithm, std.array, std.conv;
	
	private auto recSwitch(string[] args, int depth, alias N, 
T...)(string[] attrs = null)
	{		
		string str;
		auto tab = replicate("\t", depth);
		static if (T.length == 0)
		{
			string at;
			foreach(k, a; args)
			{
				at ~= "cast(Parameters!("~func~"!("~attrs.join(", 
")~"))["~to!string(k)~"])"~a;
				if (k < args.length-1) at ~= ", ";
			}
			return tab~"\treturn "~func~"!("~attrs.join(", 
")~")("~at~");\n";
		}
		else
		{
						
			str ~= tab~"switch("~N[0]~")\n"~tab~"{\n"~tab~"\tdefault: 
break;\n";
			foreach(v; __traits(allMembers, T[0]))
			{				
				mixin(`enum attr = __traits(getAttributes, 
T[0].`~v~`).stringof[6..$-1].strip();`);
				static if (attr != "")
				{
					str ~= tab~"\t"~"case "~v~":\n";					
					attrs ~= attr[1..$-1];
					str ~= recSwitch!(args, depth + 2 , N[1..$], 
T[1..$])(attrs);							
					attrs = attrs[0..$-1];
					str ~= tab~"\t\tbreak;\n";
					
				}
			}
			str ~= tab~"}\n";
			
			return str;
		}
	}

	private auto genMapper(string[] args, alias N, T...)()
	{
		string str;
		foreach(e; AliasSeq!(eT[0..eT.length/2]))
			str ~= "with("~e.stringof~") ";
		auto code = recSwitch!(args, 0, N, T)();
		return str~"\n"~code;
	}

	auto EnumMapper()
	{
             return "import std.traits;\n"~genMapper!(args, 
[eT[eT.length/2..$]], eT[0..eT.length/2])();
	}
}


Because D only half-assley implements __traits for templates, a 
lot of it is hacks and kludges.

It is used like


struct enumA
{
	int value;
	alias value this;
	 ("float") enum Float = cast(enumA)0;
	 ("int") enum Int = cast(enumA)1;
}

struct enumB
{
	int value;
	alias value this;
	 ("double") enum Double = cast(enumB)0;
	 ("byte") enum Byte = cast(enumB)1;
}

auto foo(T1, T2)(T1 a, T2 b)
{
	import std.conv;
	return to!string(a)~" - "~to!string(b);

}

void main()
{
     auto res = ()
     {
         int a = 4;
         double b = 1.23;
	    enumA enumAVal = enumA.Float;
	    enumB enumBVal = enumB.Byte;
	    mixin(EnumMapper!("foo", ["a", "b"], enumA, enumB, 
"enumAVal", "enumBVal")());
		return "--------";
     }();

	writeln(res);
	getchar();
}



and basically generates the nested switch structure:

---------------------
with(enumA) with(enumB)
switch(enumAVal)
{
	default: break;
	case Float:
		switch(enumBVal)
		{
			default: break;
			case Double:
					return foo!(float, double)(cast(Parameters!(foo!(float, 
double))[0])a, cast(Parameters!(foo!(float, double))[1])b);
				break;
			case Byte:
					return foo!(float, byte)(cast(Parameters!(foo!(float, 
byte))[0])a, cast(Parameters!(foo!(float, byte))[1])b);
				break;
		}
		break;
	case Int:
		switch(enumBVal)
		{
			default: break;
			case Double:
					return foo!(int, double)(cast(Parameters!(foo!(int, 
double))[0])a, cast(Parameters!(foo!(int, double))[1])b);
				break;
			case Byte:
					return foo!(int, byte)(cast(Parameters!(foo!(int, 
byte))[0])a, cast(Parameters!(foo!(int, byte))[1])b);
				break;
		}
		break;
}
---------------------


and so it maps the arbitrary (a,b) to the correct foo. The idea 
is simple: Given a templated function, we want map the arbitrary 
values, assuming they can be properly cast to the templated 
function depending on the enum values.

the enum values control which foo is called. But this works at 
runtime!

This is useful when one has many different representations of 
data that all can be overloaded, but one doesn't know which 
overload to use at compile time.

Could be used with variants to create automatic variant handlers 
also.

This is only useful when one templated function can handle all 
the cases though, just like how templates are used in the first 
place for overloading.

Maybe someone can clean it up and make it in to something special.
Sep 01 2017
prev sibling parent reply Andrea Fontana <nospam example.com> writes:
On Thursday, 31 August 2017 at 23:17:52 UTC, EntangledQuanta 
wrote:
 Generally one has to use a switch to map dynamic components. 
 Given a set X and Y one can form a switch to map X to Y:

 [...]
Does this work for you? https://dpaste.dzfl.pl/e2669b595539 Andrea
Sep 04 2017
parent reply EntangledQuanta <EQ universe.com> writes:
On Monday, 4 September 2017 at 09:23:24 UTC, Andrea Fontana wrote:
 On Thursday, 31 August 2017 at 23:17:52 UTC, EntangledQuanta 
 wrote:
 Generally one has to use a switch to map dynamic components. 
 Given a set X and Y one can form a switch to map X to Y:

 [...]
Does this work for you? https://dpaste.dzfl.pl/e2669b595539 Andrea
No, do you realize you are passing those enums at compile time? It won't work if they are "runtime" variables, which is the whole point of doing all this. You've essentially make a simple problem complicated. Why not just overload foo properly?
Sep 04 2017
parent Andrea Fontana <nospam example.com> writes:
On Monday, 4 September 2017 at 20:54:27 UTC, EntangledQuanta 
wrote:
 On Monday, 4 September 2017 at 09:23:24 UTC, Andrea Fontana 
 wrote:
 On Thursday, 31 August 2017 at 23:17:52 UTC, EntangledQuanta 
 wrote:
 Generally one has to use a switch to map dynamic components. 
 Given a set X and Y one can form a switch to map X to Y:

 [...]
Does this work for you? https://dpaste.dzfl.pl/e2669b595539 Andrea
No, do you realize you are passing those enums at compile time? It won't work if they are "runtime" variables, which is the whole point of doing all this. You've essentially make a simple problem complicated. Why not just overload foo properly?
So at runtime you can do in a couple of ways if i'm right: http://dpaste.com/02W9FX6 Andrea
Sep 05 2017