Thread overview
Type polymorphism and type variance
Dec 05, 2012
js.mdnq
Dec 05, 2012
Ali Çehreli
Dec 05, 2012
Lubos Pintes
Dec 05, 2012
Ali Çehreli
Dec 05, 2012
js.mdnq
Dec 05, 2012
Ali Çehreli
Dec 06, 2012
js.mdnq
Dec 06, 2012
Ali Çehreli
Dec 06, 2012
js.mdnq
Dec 06, 2012
Ali Çehreli
December 05, 2012
One thing I've always struggled with in oop is how to deal with
storing generic types.

A very simple example is, suppose you had to design a way to
store generic types.

class myGtype(T) { }

...

myGType[] gcollection;  // should store various types such as
myGtype!int, myGtype!myobj, etc.., possibly even other things
like myotherobj, etc..

Obviously we can't store different types in a homogenous array.
I know D has the ability to use variant types that basically
overcome this. I imagine it is very inefficient to do it this way?

In my mind, it seems like one could never get around the issue
without storing type information along with the data because the
compiler will eventually need to know the type information to
know how to deal with the data?

e.g.,

auto x = gcollection[i]; // x's type is not determined

auto x = cast(myGtype!int)gcollection[i]; // x's type is forced
to be myGtype!int, but may not be if gcollection is heterogeneous.

If we stored type information along with the data then we could
use it to cast to the correct type and make the compiler happy.
This seems like a rather inefficient way.


Are there any direct oop ways to do this. If, for example, I make
a wrapper class in which I can create a homogenous array of, but
somehow the wrapper class managed the heterogeneous nature of the
types, then it might work. But it seems to me, no matter how one
tries to make it work, it is impossible(at least efficiently).

In my specific case I am wanting to simply store objects in an
array of the type myObject!T where T could be any type so I can
access functions in the type(which unfortunately depend on the
type).

e.g.,

class myObject(T)
{
    void SetValue(T v) { }
}

....


myObject!?[] arr;     // Not possible
arr[0].SetValue(x)    // But what I am wanting to do.

In fact, I'll have some way to always cast x to the correct
type(or at least, if not, throw an error).


My question:

Is what I've discussed above basically doable with plan old OOP
using some design pattern and not some fancy compiler or
"tricks/hacks"? Basically, am I missing something obvious or is
the problem actually difficult and requires special methods(like
std.variant)?




December 05, 2012
On 12/04/2012 06:42 PM, js.mdnq wrote:
> One thing I've always struggled with in oop is how to deal with
> storing generic types.
>
> A very simple example is, suppose you had to design a way to
> store generic types.
>
> class myGtype(T) { }
>
> ...
>
> myGType[] gcollection; // should store various types such as
> myGtype!int, myGtype!myobj, etc.., possibly even other things
> like myotherobj, etc..
>
> Obviously we can't store different types in a homogenous array.
> I know D has the ability to use variant types that basically
> overcome this. I imagine it is very inefficient to do it this way?
>
> In my mind, it seems like one could never get around the issue
> without storing type information along with the data because the
> compiler will eventually need to know the type information to
> know how to deal with the data?
>
> e.g.,
>
> auto x = gcollection[i]; // x's type is not determined

What do you expect to do with x? Unless there is a common interface the compiler cannot compile the code without knowing the type of x.

Note that even myGtype!int and myGtype!double are completely different types with completely different capabilities.

> auto x = cast(myGtype!int)gcollection[i]; // x's type is forced
> to be myGtype!int, but may not be if gcollection is heterogeneous.
>
> If we stored type information along with the data then we could
> use it to cast to the correct type and make the compiler happy.

Alas, the compiler is not available at runtime.

> Are there any direct oop ways to do this.

The easiest way in OOP would be interfaces and polymorphism. The myGtype class template below implements the MyInterface interface and it enables us to put different types of objects in a collection.

import std.stdio;

interface MyInterface
{
    void foo();
    MyInterface dup();
}

class myGtype(T) : MyInterface
{
    void foo()
    {
         specialOperationFor!T();
    }

    myGtype dup()
    {
        return new myGtype();
    }
}

void specialOperationFor(T : double)()
{
    writefln("Special operation for %s", T.stringof);
}

struct S
{
    int i;
}

void specialOperationFor(T : S)()
{
    writefln("Special operation for %s", T.stringof);
}

void main()
{
    MyInterface[] objects;

    objects ~= new myGtype!double();
    objects ~= new myGtype!int();
    objects ~= new myGtype!S();

    foreach (o; objects) {
        o.foo();
    }

    // We can even copy them and the copies have the correct types:
    foreach (i, o; objects) {
        auto c = o.dup();
        writefln("Using the copy of item %s", i);
        c.foo();
    }
}

The output:

Special operation for double
Special operation for int
Special operation for S
Using the copy of item 0
Special operation for double
Using the copy of item 1
Special operation for int
Using the copy of item 2
Special operation for S

Ali

December 05, 2012
Sorry maybe I am stupid but where is the value of concrete T?
Or perhaps I completely misunderstood this?
Dňa 5. 12. 2012 4:59 Ali Çehreli  wrote / napísal(a):
> On 12/04/2012 06:42 PM, js.mdnq wrote:
>  > One thing I've always struggled with in oop is how to deal with
>  > storing generic types.
>  >
>  > A very simple example is, suppose you had to design a way to
>  > store generic types.
>  >
>  > class myGtype(T) { }
>  >
>  > ...
>  >
>  > myGType[] gcollection; // should store various types such as
>  > myGtype!int, myGtype!myobj, etc.., possibly even other things
>  > like myotherobj, etc..
>  >
>  > Obviously we can't store different types in a homogenous array.
>  > I know D has the ability to use variant types that basically
>  > overcome this. I imagine it is very inefficient to do it this way?
>  >
>  > In my mind, it seems like one could never get around the issue
>  > without storing type information along with the data because the
>  > compiler will eventually need to know the type information to
>  > know how to deal with the data?
>  >
>  > e.g.,
>  >
>  > auto x = gcollection[i]; // x's type is not determined
>
> What do you expect to do with x? Unless there is a common interface the
> compiler cannot compile the code without knowing the type of x.
>
> Note that even myGtype!int and myGtype!double are completely different
> types with completely different capabilities.
>
>  > auto x = cast(myGtype!int)gcollection[i]; // x's type is forced
>  > to be myGtype!int, but may not be if gcollection is heterogeneous.
>  >
>  > If we stored type information along with the data then we could
>  > use it to cast to the correct type and make the compiler happy.
>
> Alas, the compiler is not available at runtime.
>
>  > Are there any direct oop ways to do this.
>
> The easiest way in OOP would be interfaces and polymorphism. The myGtype
> class template below implements the MyInterface interface and it enables
> us to put different types of objects in a collection.
>
> import std.stdio;
>
> interface MyInterface
> {
>      void foo();
>      MyInterface dup();
> }
>
> class myGtype(T) : MyInterface
> {
>      void foo()
>      {
>           specialOperationFor!T();
>      }
>
>      myGtype dup()
>      {
>          return new myGtype();
>      }
> }
>
> void specialOperationFor(T : double)()
> {
>      writefln("Special operation for %s", T.stringof);
> }
>
> struct S
> {
>      int i;
> }
>
> void specialOperationFor(T : S)()
> {
>      writefln("Special operation for %s", T.stringof);
> }
>
> void main()
> {
>      MyInterface[] objects;
>
>      objects ~= new myGtype!double();
>      objects ~= new myGtype!int();
>      objects ~= new myGtype!S();
>
>      foreach (o; objects) {
>          o.foo();
>      }
>
>      // We can even copy them and the copies have the correct types:
>      foreach (i, o; objects) {
>          auto c = o.dup();
>          writefln("Using the copy of item %s", i);
>          c.foo();
>      }
> }
>
> The output:
>
> Special operation for double
> Special operation for int
> Special operation for S
> Using the copy of item 0
> Special operation for double
> Using the copy of item 1
> Special operation for int
> Using the copy of item 2
> Special operation for S
>
> Ali
>

December 05, 2012
On Wednesday, 5 December 2012 at 03:59:29 UTC, Ali Çehreli wrote:
> On 12/04/2012 06:42 PM, js.mdnq wrote:
> > One thing I've always struggled with in oop is how to deal
> with
> > storing generic types.
> >
> > A very simple example is, suppose you had to design a way to
> > store generic types.
> >
> > class myGtype(T) { }
> >
> > ...
> >
> > myGType[] gcollection; // should store various types such as
> > myGtype!int, myGtype!myobj, etc.., possibly even other things
> > like myotherobj, etc..
> >
> > Obviously we can't store different types in a homogenous
> array.
> > I know D has the ability to use variant types that basically
> > overcome this. I imagine it is very inefficient to do it this
> way?
> >
> > In my mind, it seems like one could never get around the issue
> > without storing type information along with the data because
> the
> > compiler will eventually need to know the type information to
> > know how to deal with the data?
> >
> > e.g.,
> >
> > auto x = gcollection[i]; // x's type is not determined
>
> What do you expect to do with x? Unless there is a common interface the compiler cannot compile the code without knowing the type of x.
>
> Note that even myGtype!int and myGtype!double are completely different types with completely different capabilities.
>
> > auto x = cast(myGtype!int)gcollection[i]; // x's type is
> forced
> > to be myGtype!int, but may not be if gcollection is
> heterogeneous.
> >
> > If we stored type information along with the data then we
> could
> > use it to cast to the correct type and make the compiler
> happy.
>
> Alas, the compiler is not available at runtime.
>
> > Are there any direct oop ways to do this.
>
> The easiest way in OOP would be interfaces and polymorphism. The myGtype class template below implements the MyInterface interface and it enables us to put different types of objects in a collection.
>
> import std.stdio;
>
> interface MyInterface
> {
>     void foo();
>     MyInterface dup();
> }
>
> class myGtype(T) : MyInterface
> {
>     void foo()
>     {
>          specialOperationFor!T();
>     }
>
>     myGtype dup()
>     {
>         return new myGtype();
>     }
> }
>
> void specialOperationFor(T : double)()
> {
>     writefln("Special operation for %s", T.stringof);
> }
>
> struct S
> {
>     int i;
> }
>
> void specialOperationFor(T : S)()
> {
>     writefln("Special operation for %s", T.stringof);
> }
>
> void main()
> {
>     MyInterface[] objects;
>
>     objects ~= new myGtype!double();
>     objects ~= new myGtype!int();
>     objects ~= new myGtype!S();
>
>     foreach (o; objects) {
>         o.foo();
>     }
>
>     // We can even copy them and the copies have the correct types:
>     foreach (i, o; objects) {
>         auto c = o.dup();
>         writefln("Using the copy of item %s", i);
>         c.foo();
>     }
> }
>
> The output:
>
> Special operation for double
> Special operation for int
> Special operation for S
> Using the copy of item 0
> Special operation for double
> Using the copy of item 1
> Special operation for int
> Using the copy of item 2
> Special operation for S
>
> Ali

[Sorry about all the dups, it seems many times lately the forum is not accepting my posts, seems like it went through this time even though it said it didn't]

Yeah, I basically understand that Ali, I wrote another post that updated what I was doing that was more pertinent.

Your method is akin to just storing the objects as object types but you end up wrapping them in a type to encapsulate the template. You did answer my question but unfortunately I basically asked the wrong question(or at least too simplified).

---------------------

I am trying to create a direct acyclic graph where nodes are objects and edges are functions between the nodes.

R[T in (T1, T2,...)][F] nodes;

so nodes[somenode] is an array of objects of types that depend on the specific nodes.

nodes[somenode][1] will be, say, a function from somenode to nodes[somenode][1].

These functions will take the nodes, which can be of different types, and compute some value on them.

R will be a function ptr that takes two nodes. (but it is variant)




e.g.,

myNode!A n1;
myNode!B n2;

bool function(myNode!A, myNode!B) compn1n2;

compn1n2(n1,n2);

is the basic idea. But every pair of connected nodes will possibly have a different function associated with it.

While I can use a common interface of the nodes and even that of the edges I don't see how I can pass around data between the two without resorting to passing objects and storing type information.


If this is possible maybe you can extend your example

class myGtype(T) : MyInterface
{
    T Value;   // <-------- added
    void foo()
    {
         specialOperationFor!T();
    }

    myGtype dup()
    {
        return new myGtype();
    }
}

...

    foreach (o; objects) {
        o.foo();
    }

but instead I need to do something like

auto value = o[0].foo(o[1]);

where value is then some computation between o[0] and o[1].

(in general I'll have to random objects of a common interface stored in my array that I want to do a computation on using a function associated with them)

Maybe all that is needed is another level in encapsulation to hide away the template parameters sort of like what you did? (and it will need to be done not only on the nodes but the edges to be able to store them too)

Thanks for the help.

(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).)






December 05, 2012
On 12/05/2012 04:28 AM, Lubos Pintes wrote:

> where is the value of concrete T?
> Or perhaps I completely misunderstood this?

Maybe I misunderstand you. :) Are you pointing out that the class template that I have shown did not have any member variables? If so, they can have anything they want.

I have changed the program to include different members and even specialization (for dchar):

import std.stdio;

/* This defines what I want to do with my objects: */
interface MyInterface
{
    void foo();

    /* This is just a silly function to demonstrate that copying an object
     * must be the responsibility of that object because we do not know the
     * concerete type at this level. */
    MyInterface dup();
}

/* A class template that implements the interface */
class myGtype(T)
    if (!is(T == dchar))
  : MyInterface
{
    /* Here is a concrete value: */
    T value;

    /* The implementation can be arbitrarily complex for different T: */
    static if (is (T == S)) {
        S[string] aa;
    }

    this(T value)
    {
        this.value = value;

        static if (is (T == S)) {
            aa["hello"] = value;
        }
    }

    void foo()
    {
        /* This demonstrates how operations that depend on T can be used
         * inside this class template.
         *
         * specialOperationFor() could alternatively be a member function
         * template but I find it simpler when it is a free-standing function.
         */
        specialOperationFor!T(value);
    }

    /* This one simply uses 'value'. It could do anything else. */
    myGtype dup()
    {
        return new myGtype(value);
    }
}

/* It is possible to implement a specialization of the type completely
 * separately: */
class myGtype(T)
    if (is(T == dchar))
    : MyInterface
{
    void foo()
    {
        writeln("Inside myGtype!dchar.foo");
    }

    myGtype dup()
    {
        return new myGtype();
    }
}

void specialOperationFor(T : double)(double d)
{
    writefln("Special operation for double: %s", d);
}

struct S
{
    int i;
}

void specialOperationFor(T : S)(S s)
{
    writefln("Special operation for S: %s", s);
}

void main()
{
    MyInterface[] objects;

    objects ~= new myGtype!double(1.5);
    objects ~= new myGtype!int(42);
    objects ~= new myGtype!S(S(100));
    objects ~= new myGtype!dchar();

    foreach (o; objects) {
        o.foo();
    }

    // We can even copy them and the copies have the correct types:
    foreach (i, o; objects) {
        auto c = o.dup();
        writefln("Using the copy of item %s", i);
        c.foo();
    }
}

Ali

December 05, 2012
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

December 06, 2012
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.













December 06, 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

December 06, 2012
On Thursday, 6 December 2012 at 03:22:55 UTC, Ali Çehreli wrote:
> 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


Ok, I see I can use a class to wrap a generic function and then store the class. For some unknown I didn't think it was possible. When doing it though, there seems to be a ton of overhead.

Remember though, the return type of the functions have to match the node type they are associated with.

For example, if B is a node and B.Edge if the "function" that maps other nodes's values to B's value then B.Edge must have the same type as B.Value.

essentially B.Value = B.Edge(A, C, Q) // or whatever

I'll study your code as there seems to be a lot of "goodies" in it that I need to learn.

Thanks
December 06, 2012
On 12/06/2012 12:08 PM, js.mdnq wrote:
> On Thursday, 6 December 2012 at 03:22:55 UTC, Ali Çehreli wrote:

The following comment is relevant:

>> /* 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
>> {}

> Remember though, the return type of the functions have to match the node
> type they are associated with.

If HandlingResult is an interface, then the Edge functions can all return a descendent of that interface. (In OOP speak, edgeHandler() functions would have "covariant return types".)

Ali