Type polymorphism and type variance
Ali Çehreli
acehreli at yahoo.com
Wed Dec 5 19:22:53 PST 2012
On 12/05/2012 04:40 PM, js.mdnq wrote:
> On Wednesday, 5 December 2012 at 22:53:11 UTC, Ali Çehreli wrote:
>> On 12/05/2012 09:51 AM, js.mdnq wrote:
>>
>> > (if your having trouble understanding the problem then just
>> think of how
>> > you would efficiently store the nodes and edges of a directed
>> acyclic
>> > graph. Each node is of a somewhat arbitrary type and edges
>> are functions
>> > over the nodes that do computations, possibly changing the
>> nodes
>> > "value". These all need to be stored someway so that a full
>> computation
>> > chain can be carried out(the graph structure is arbitrary).)
>>
>> I do not fully understand the issue yet but I smell "double dispatch"
>> in there. There is no elegant solution of double dispatch at least in
>> C++ but there are a number of solutions with certain compromises. I
>> will read your post more carefully later.
>>
>> Ali
>
> Thanks, I'll look at yours too when my head is more clear. I did have a
> better post but it didn't get through.
>
> Here is possibly a better overview:
>
> http://faculty.ucr.edu/~hanneman/nettext/Figure7_11.jpg
>
> is a good representation of what I'm trying to do.
>
> The way I'm thinking about it is that for each node there is a function
> that takes the data from all the other nodes that are coming to it.
>
> So, A's function is nill, B's function takes 2 values A.Data and D.Data,
> C's function takes B.Data and C.Data, D's function takes B.Data and
> E.Data, and E's function takes B.Data.
>
> Each function is different as it will not necessarily be the same
> computation and the data in each node can be of a different type.
>
> This is easy to do by just passing around an array of objects and their
> types then using a switch to typecast. Each "function" know exactly what
> data it's trying to use though unless the "user" made a mistake as they
> will supply the function they want to use.
>
> The edge direction also specifies the flow the computations as I will
> iterate through the graph computing values on nodes. In my case I will
> expect that a "cycle" of computations will be nilpotent. This keeps any
> sequence of computations from creating unstable data that does stuff
> like "toggles" back and forth or jumping around after each computation.
>
> For example,
>
> If A.Data is an int, B.Data is a bool, and D.Data is a double then the
> user will supply a function that takes an int and a double that returns
> a bool.
>
>
> There just has to be a better way that avoids casting everything to an
> object and using the very slow variant type. ;/ The issue is mainly
> about storing the objects because this is very easy to do statically.
>
> Thanks again.
>
Here is a solution that stores the types of the points in delegates.
That is the "static information" that we know at compile time:
import std.stdio;
/* Although this can be a polymorphic type, I am assuming that all of
the edge
* handlers will produce the same type of result. */
struct HandlingResult
{}
/* This is the edge handler for (int, double) as well as (double, int). (See
* below.) */
HandlingResult edgeHandler(int i, double d)
{
writefln("Handling %s and %s", i, d);
return HandlingResult();
}
struct S
{
int i;
}
/* This is the edge handler for (int, S) and (S, int). */
HandlingResult edgeHandler(int i, S s)
{
writefln("Handling %s and %s", i, s);
return HandlingResult();
}
/* This defines what gets done with edges. */
interface IEdge
{
HandlingResult handle();
}
class Edge(T0, T1) : IEdge
{
alias HandlingResult function(T0, T1) Handler;
/* Each edge consists of two values and a handling function. */
T0 value0;
T1 value1;
Handler func;
this(T0 value0, T1 value1, Handler func)
{
this.value0 = value0;
this.value1 = value1;
this.func = func;
}
/* Simply dispatches to the corresponding function. */
HandlingResult handle()
{
return func(value0, value1);
}
}
/* I've imagined that the function that handles int,double would handle
* double,int as well. This template tries the arguments both ways.
*
* Note 1: This is just a draft. It should probably also detect
ambiguities.
*
* Note 2: I had to use delegates as I think there is no way of taking the
* address of a specific overload of a function.
*/
template HandlerFuncSelector(T0, T1)
{
static if (__traits(compiles, edgeHandler(T0.init, T1.init))) {
enum result = (T0 a, T1 b) => edgeHandler(a, b);
} else static if (__traits(compiles, edgeHandler(T1.init, T0.init))) {
enum result = (T0 a, T1 b) => edgeHandler(b, a);
} else {
static assert(false, "No edgeHandler("
~ T0.stringof ~ ", " ~ T1.stringof ~ ") defined");
}
}
/* The convenience function to make an Edge after choosing its handler. */
Edge!(T0, T1) edge(T0, T1)(T0 value0, T1 value1)
{
return new Edge!(T0, T1)(value0, value1,
HandlerFuncSelector!(T0, T1).result);
}
void main()
{
IEdge[] edges;
/* These will be handled by edgeHandler(int,double). */
edges ~= edge(1, 1.5);
edges ~= edge(2.5, 2);
/* These will be handled by edgeHandler(int,S). */
edges ~= edge(3, S(100));
edges ~= edge(S(200), 4);
foreach (edge; edges) {
edge.handle();
}
}
Ali
More information about the Digitalmars-d-learn
mailing list