New programming paradigm

EntangledQuanta via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Thu Sep 7 09:55:02 PDT 2017


On Thursday, 7 September 2017 at 14:28:14 UTC, Biotronic wrote:
> On Wednesday, 6 September 2017 at 23:20:41 UTC, EntangledQuanta 
> wrote:
>> So, no body thinks this is a useful idea or is it that no one 
>> understands what I'm talking about?
>
> Frankly, you'd written a lot of fairly dense code, so 
> understanding exactly what it was doing took a while. So I sat 
> down and rewrote it in what I'd consider more idiomatic D, 
> partly to better understand what it was doing, partly to 
> facilitate discussion of your ideas.
>
> The usage section of your code boils down to this:
>


Sorry, I think you missed the point completely... or I didn't 
explain things very well.

I see no where in your code where you have a variant like type.


What I am talking about is quite simple: One chooses the correct 
template to use, not at compile time based on the type(like 
normal), but at runtime based on a runtime variable that 
specifies the type. This is has variants are normally used except 
one must manually call the correct function or code block based 
on the variable value.


Here is a demonstration of the problem:


import std.stdio, std.variant, std.conv;



void foo(T)(T t)
{
	writeln("\tfoo: Type = ", T.stringof, ", Value = ", t);
}

void bar(Variant val)
{
	writeln("Variant's Type = ", to!string(val.type));

	// foo called with val as a variant
	foo(val);

	writeln("Dynamic type conversion:");
	switch(to!string(val.type))
	{
		case "int": foo(val.get!int); break;	// foo called with val's 
value as int
		case "float": foo(val.get!float); break;	// foo called with 
val's value as float
		case "immutable(char)[]": foo(val.get!string); break;	// foo 
called with val's value as string
		case "short": foo(val.get!short); break;	// foo called with 
val's value as short
		default: writeln("Unknown Conversion!");
	}


}

void main()
{
	Variant val;
	writeln("\nVarant with int value:");
	val = 3;
	bar(val);
	writeln("\n\nVarant with float value:");
	val = 3.243f;
	bar(val);
	writeln("\n\nVarant with string value:");
	val = "XXX";
	bar(val);
	writeln("\n\nVarant with short value:");
	val = cast(short)2;
	bar(val);

	getchar();
}

Output:

Varant with int value:
Variant's Type = int
         foo: Type = VariantN!20u, Value = 3
Dynamic type conversion:
         foo: Type = int, Value = 3


Varant with float value:
Variant's Type = float
         foo: Type = VariantN!20u, Value = 3.243
Dynamic type conversion:
         foo: Type = float, Value = 3.243


Varant with string value:
Variant's Type = immutable(char)[]
         foo: Type = VariantN!20u, Value = XXX
Dynamic type conversion:
         foo: Type = string, Value = XXX


Varant with short value:
Variant's Type = short
         foo: Type = VariantN!20u, Value = 2
Dynamic type conversion:
         foo: Type = short, Value = 2

The concept to gleam from this is that the switch calls foo with 
the correct type AT compile time. The switch creates the mapping 
from the runtime type that the variant can have to the compile 
time foo.

So the first call to foo gives: `foo: Type = VariantN!20u, Value 
= 2`. The writeln call receives the val as a variant! It knows 
how to print a variant in this case, lucky for us, but we have 
called foo!(VariantN!20u)(val)!

But the switch actually sets it up so it calls 
foo!(int)(val.get!int). This is a different foo!

The switch statement can be seen as a dynamic dispatch that calls 
the appropriate compile time template BUT it actually depends on 
the runtime type of the variant!

This magic links up a Variant, who's type is dynamic, with 
compile time templates.

But you must realize the nature of the problem. Most code that 
uses a variant wouldn't use a single template to handle all the 
different cases:

	switch(to!string(val.type))
	{
		case "int": fooInt(val.get!int); break;	
		case "float": fooFloat(val.get!float); break;	
		case "immutable(char)[]": fooString(val.get!string); break;	
		case "short": fooShort(val.get!short); break;
		default: writeln("Unknown Conversion!");
	}



These functions might actually just be code blocks to handle the 
different cases.

Now, if you understand that, the paradigm I am talking about is 
to have D basically generate all the switching code for us 
instead of us ever having to deal with the variant internals.

We have something like

void bar(var t)
{
	writeln("\tbar: Type = ", t.type, ", Value = ", t);
}


AND it would effectively print the same results. var is akin to 
variant but the compiler understands this and generates N 
different bar's internally and a switch statement to dynamically 
call the desired one at runtime, yet, we can simply call bar with 
any value we want.

e.g.,

void main()
{
    bar(3); // calls bar as if bar was `void bar(int)`
    bar(3.4f); // calls bar as if bar was `void bar(float)`
    bar("sad"); // calls bar as if bar was `void bar(string)`
}

and the writeln in bar never sees a variant type(just like it 
doesn't in the first case with the switches).

As you can see, the code is much much less verbose. No switch 
statements are written by hand. bar is sort of like a template 
because it can accept different types.

The code I wrote simulates the above by using a mixin template to 
generate the switches and uses a template to call the right bar:

enum Types
{
    Int,
    Float,
    String,
    Short
}

void bar(T)(T t)
{
     writeln("\tbar: Type = ", t.type, ", Value = ", t);
}

void main()
{
    mixin(EnumMapper!("bar", Types, 3));
    mixin(EnumMapper!("bar", Types, 3.4f));
    mixin(EnumMapper!("bar", Types, "sad"));
}

Note that each mixin is a switch statement.

It's still much shorter than the original solution, specially if 
one uses dynamic types a lot... but still quite verbose. Imagine 
if the compiler did this for us!! Not only would it be very 
elegant it is also fast because it's just a switch statement. It 
basically turns dynamic types in to compile time types(at the 
cost of the switches, which is just a cmp and jmp instruction). 
dynamic in C# is pretty slow because it has to do a bit more than 
this, since it doesn't actually have a compile time template like 
D has.

Is that more clear? What it would allow us to do, if we wanted, 
is to never use auto or any explicit types and we would have 
something like a D version of a dynamically typed language(again, 
at the cost of the switches, but it's all hidden from us).

Of course, there are two problems with this: 1. The types have to 
be known at compile time(so it can generate the switch, although 
'default' could be used to handle unknown cases using standard 
techniques). 2. At some point we generally will have to do some 
type of manual switch on the types. Writeln, for example, does 
this internally for us, which is why I used it. If the types are 
confluent, then we can get away with out doing this such as the 
confluent primitive types. e.g., if we were just adding two 
numbers, we could have

auto bar(var t1, var t2) { return t1 + t2; }

and it would work as long as t1 and t2 were confluent.

What's more is that if we later decide we want to use only a 
specific type, we don't have to change anything but could simply 
change var to one of the types such as int, and the compiler and 
optimize out the switch.

What's more, is that we can return var's and this allows us to do 
do things like

var foo(var x)
{
    if (x == 3)
     return x;
    return "error!";
}

or whatever. It's clear, concise, fast, and bridges the gap 
between compile time and runtime variables. It obviously can't 
solve the impossible but does provide a nice solution for certain 
problems and for simple uses. I think the main drawback is that 
one can't really use this to the extreme because the code will 
explode both in size and performance cost. All those switches 
will add up in both memory size and cycles... I'm not sure if 
that would effect normal usage though.






More information about the Digitalmars-d-learn mailing list