Polymorphic generics

M

MRe

Hi,

Is the following possible? It feels like it's possible - but for the
sake of example, I've made up something that isn't possible. Hopefully
it will explain my question, because I'm having a bit of trouble
putting it into words. I am able to do what I want by using reflection
(but would be interested in avoiding it) and I've included an example
of it too (below my first example).

Note: the only difference between the two examples is in the Link.find
method.

Thank you,
Kind regards,
Eliott

//////////// Desired (but non-compiling) "solution" ////////////

using System;

namespace Test
{

class Program
{

static void Main
( string[] args
){ new Program();
}

private Program()
{

// Create a link list using
// Link class (defined below)
Link list = new Link
( new Link
( new SpecialLink()
)
);

// Find the "special" link
SpecialLink specialness = null;
if(list.find<SpecialLink>
( ref specialness
)) specialness.unleash();

}

}

// A generic link
class Link
{

public Link()
{ _next = null;
}

public Link(Link next)
{ _next = next;
}

// Find a link that knows
// about some T type
public virtual bool find<T>
( ref T link
) where T : Link
{ if(_next != null)
return _next.find<T>
( ref link
);
return false;
}

private readonly Link _next;

}

// A less generic link
class SpecialLink : Link
{

public SpecialLink() : base()
{ }

public SpecialLink
( Link next
): base(next)
{ }

// My magical polymorphic generics
// say this overriding find will
// be called in the face of
// find<SpecialLink>(..). I
// arbitrarily imagine reuse of
// the "where" keyword to mean
// polymorphic generic override
public override bool find<T>
( ref T link
) where T : SpecialLink
{ link = this;
return true;
}

public void unleash()
{ Console.WriteLine("hi");
}

}

}

//////////// Undesired (but compiling) "solution" ////////////

using System;

namespace Test
{

class Program
{

static void Main
( string[] args
){ new Program();
}

private Program()
{

// Create a link list using
// Link class (defined below)
Link list = new Link
( new Link
( new SpecialLink()
)
);

// Find the "special" link
SpecialLink specialness = null;
if(list.find<SpecialLink>
( ref specialness
)) specialness.unleash();

}

}

// A generic link
class Link
{

public Link()
{ _next = null;
}

public Link(Link next)
{ _next = next;
}

// Find a link that knows
// about some T type
public virtual bool find<T>
( ref T link
) where T : Link
{ // "If it's not broken, don't
// fix it!" depends on the
// definition of "broken."
// ..and this needs a fixin'
if(this is T)
{ link = (T)this;
return true;
}
if(_next != null)
return _next.find<T>
( ref link
);
return false;
}

private readonly Link _next;

}

// A less generic link
class SpecialLink : Link
{

public SpecialLink() : base()
{ }

public SpecialLink
( Link next
): base(next)
{ }

public void unleash()
{ Console.WriteLine("hi");
}

}

}
 
J

Jeff Johnson

Is the following possible? It feels like it's possible - but for the
sake of example, I've made up something that isn't possible. Hopefully
it will explain my question, because I'm having a bit of trouble
putting it into words. I am able to do what I want by using reflection
(but would be interested in avoiding it) and I've included an example
of it too (below my first example).

I didn't read too much, but from the subject it sounds like what you want is
covariance and contravariance, and those are only available in C# 4.0
(Framework 4.0, VS 2010). Search for those two words and see if the
description is what you're looking for.
 
M

MRe

Hi Pete,

Thank you for the detailed reply.
The use of "is" is only in the broadest sense a use of reflection.

I had debated with myself whether to use the word "reflection,"
considering what it's used for. I just couldn't think of another word
that was as obvious about the context in which I was referring to
"is."
Whether you call it "reflection" or not, it's not clear to me what the
motivation for avoiding the "is" operator is.

It's not specifically "is" that I'm trying to avoid, it's the
downcasting - [generic] Link is doing the work that SpecialLink should
be doing, i.e., { link = (T)this; }
I might clean up the code
just a little by writing it like this:
T t = this as T;
if (t != null)
{
link = t;
return true;
}

I keep forgetting there's an "as" operator :p
In fact, if intended design is to always find "findable" types override a
method and a constraint together, it seems to me that would actually
complicate the lives of inheritors of the base class.

I had kind of seen it as, if the inheriting class didn't override the
find method with the types it understood, it would fall back on the
generic implementation. So inheriting classes wouldn't have to
implement it if they didn't want to. Another thing would be that (if T
wasn't constrained to types of Link) it could be any type, so
SpecialLink could have like { bool find<T>(ref T something) where T :
SpecialLinkConfig {} } where SpecialLinkConfig is not a Link object,
just something only SpecialLink holds. Then the calling code could
then ask the linked-list, "who here knows about SpecialLinkConfig,"
and SpecialLink would respond. It would be, in a way, similar to
having SpecialLink implement some ISpecialLinkConfig, and using the
"working" version.

For some reason, I often find myself wanting this feature. It sort of
feels like, if (in real life) I need a mechanic, I could ask in a
group of people, and the people who were not mechanics might say, "I
don't know, maybe ask that guy." Each passing me on until I hit on a
mechanic. So, this would be a linked-list of Person objects, where one
was a Mechanic:person. If I were to program this in an OO way, I would
imagine I'd have to put "IsMechanic" as an abstract method into the
Person object, which seems like overkill. Then there's the "working"
version: ask a Person to reflect on whether they're a Mechanic, they
look at themselves and see they're in a mechanic's clothing, and so
say yes, and then downcast to one. Finally, there's the imagined one,
which would be more like, I ask one Person (who's a Doctor:person for
example) are you a Mechanic. As a Doctor, they have nothing to say, so
revert to being a Person, which makes them say, "Ask that guy." But if
they're a Mechanic, they just say yes, as a Mechanic, not a Person. It
makes sense to me that way.
On top of that, note that two versions you've posted are not actually
semantically identical.

Hmm, I didn't spot that (I'm still not sure I do). I had intended them
to be semantically identical.
Assuming that the semantics of your "working" version are correct or at
least acceptable, I've included (see below) yet another version that
IMHO is better than either of your proposals

The code I posted was the simplest complete example I could come up
with to explain my problem. It's quite different from my actual code
(which uses a different data structure). But I do like to make use
of .NETs own features and agree your updated version is better.

Thank you kindly Pete,
Eliott
 
M

MRe

Hi Jeff,

Thank you for the reply
I didn't read too much, but from the subject it sounds like what you want is
covariance and contravariance

This, as Peter said, isn't what I was looking for. But the strangest
thing - I was hacking away last night and hit on a problem that needed
this very solution. I was like, "Hu, I can solve this with my new-
learned covariance knowledge. Wow!" How did you know?

Thank you,
Kind regards,
Eliott
 
M

MRe

Hi again Pete,

And thank you again for your reading/posting.
My point is that I don't see why inheriting classes would _ever_ want
to.  The cast isn't something that necessarily should be avoided

I suppose - I guess my only reasons are to avoid the cast, because it
feels like an unsafe down-cast.
[...]do you imagine that only the list objects should inspect their
children? Or should children inspect their children too? If the
latter, then I would think a tree-based algorithm and data structure
would make more sense.
[...]
If the second of the two possibilities applies to your case, then
the whole design seems a bit fishy to me.
[...]
Just picking the first object of some arbitrary type that you can find
seems like a serious maintenance headache to me.

It could be arbitrary objects.. The actual thing I'm working on is an
AST (so, yes to it also; it is a tree structure). Nodes at any level
may need to locate a node at any other level to get/set information
held by that node, or information that node has reference to.

The reason I don't simply evaluate from leaf to root, passing all
required information back up the tree, is that the nodes don't
evaluate up the tree, but into a [committed] stream (so, sometimes I
need to examine a leaf (and alter its properties) indirectly through
the branches while I'm in a root (and sometimes, the other-way-
round)), also, the root node may have to take the leaf node out of
it's current position and evaluate it ahead of its parents.

It sounds complicated, but it certainly makes the job of the end-user
(the one who has to write the code that becomes the AST) significantly
easier. It's for a fairly loosely defined (scripting-type) language
with no particular notion of scope or order (so the AST has to hunt
for things). Sounds like a debugging nightmare of a language, but it's
a functional language with no recursion and a graphical output, so
debugging is just a matter of looking at some stateless code, or the
output picture it prints.
In the code example you posted, had the constraint-override version
worked, you would have found that the find<T>() method would return an
object only if the type of the object you were looking for overrode the
method as your SpecialLink class did.

The base Link class doesn't do any checking for type and so if, for
example, you call it as find<Link>() instead of find<SpecialLink>(), it
will always return "false" without setting the "link" argument, even if
the list is non-empty (and thus certainly contains Link objects).

I kind of imagine it working like this.. (this is more of a C++
template type look at it, but as a concept, it seems sound to me
(note: I use the formal parameter {ref T link} version because this
concept relies on method overloading)

// The classes with their generic set-ups
class Link
{
public virtual bool find<T>(ref T link)
where T : Link
{ .. }
}
class SpecialLink : Link
{
public override bool find<T>(ref T link)
where T : SpecialLink
{ .. }
}
// Give the generics some types to play with
void someFunction()
{ Link link;
SpecialLink specialLink;
rootLink.find<Link>(ref link);
rootLink.find<SpecialLink>(ref specialLink);
}

// Looking at it in a C++ template style,
// (where the template type is preprocessed
// to a specific type) - I know it's not
// how generics works, but, I don't think
// it makes it imposable for such a thing
// to be conceive..

// So, the following code is "generated" by
// the compiler

class Link
{
public virtual bool find(ref Link link)
{ .. }
}
class SpecialLink : Link
{
// This appears to override a method that
// doesn't exist in the base class, but it's
// OK because it can be implied by the
// compiler when it compiles SpecialLink,
// because it can relate it back to bool
// find<T>(ref T link). i.e., it knows that
// passing this SpecialLink type to the base
// class will up-cast to Link. Otherwise, it
// will go to this method. So, for the sake of
// example, the polymorphic generics collapses
// to a overridden, overloaded method
public override bool find(ref SpecialLink link)
{ .. }
}

So, there's no dependency on "Link" to know about "SpecialLink". If
find(whatever), where "whatever" is a type of "Link" (even
"SpecialLink"), is called on any "Link" node, any will be able to up-
cast to Link, everything's fine. However, if "whatever" is a type of
SpecialLink and is called on the SpecialLink object, it will use the
overloaded SpecialLink method. The only limit is that base.find can't
be called.
Hope that helps!

Pete

I suppose I'm more rambling now, because I'm liking the casting idea,
simply because you are OK with it. I do appreciate all your posts (not
just the ones you've replied to me).

Thank you again Pete,
Kind regards,
Eliott
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top