views:

243

answers:

8

I hope this question isn't deemed too subjective - I don't really expect a definitive answer, but I hope that everyone's opinion will at least help me form my own.

I'm implementing a custom type system, that is a superset of the classical OOP type system. In this type system, object instances may be combined at runtime to form new instances, while retaining individual identities.

This code:

var p = new Person();
var pa = new Partner(p);

...results in a single combined object, with "p" and "pa" being different OOP-conforming views on it. IOW, changing a property value on one of the views is immediately reflected on any other view that also contains this property.

This all works fine and well, but it's missing two key API's for querying type identities. I would really like to be able to write such code:

if (p is Partner)
{
    (p as Partner).SomePartnerProperty = "...";
}

This of course doesn't work, because the behaviour of "is" and "as" operators can't be overloaded/extended beyond what .NET's OOP rules dictate. Nontheless, I still need this feature in my type system.

My first thought was to use generic extension methods that would attach to all instances of my type system:

public static bool Is<T>(this BaseType target) where T : BaseType { ... }
public static T As<T>(this BaseType target) where T : BaseType { ... }

Ignoring the issue of name conflict in case-insensitive languages, this seems OK in terms of functionality:

if (p.Is<Partner>())
{
    p.As<Partner>().SomePartnerProperty = "...";
}

However, I can't help but wonder - is this really the nicest, most convenient API one can come up with?

How would you advise I implement these two operators so they feel natural to use in application code?

UPDATE: For anyone that is wondering about the purpose of such type system... Basically, each type falls into one of two categories: Identity or Role. In the example I gave above, Person is an Identity (by design), while Partner is a Role (again, by design - it could have been designed differently). The ground rule of this type system is that any number of Roles may be composed with any given Identity, while Identity itself may only be composed with a higher Identity (e.g. a Person may become a Contact, but can never become a Company). Such type system enables applications to transparently deal with e.g. Partner objects, regardless of what Identity they have (e.g. Person, Company, Bank, etc.).

+1  A: 

I'd suggest to use two simple functions.

The first one would (at least for me) be intuitive with a name like:

p.IsType(typeHere)
p.IsPartner()

and the second a simple To-Call:

p.ToPartner()

I wouldn't implement those as generics (just doesn't feel right for me), especially the last one not.

Bobby
Bobby, how do you feel about the OfType<T>() method in Linq? Generics seems like a good fit here to me...
Rob Fonseca-Ensor
I think he's looking to implement an actual type system, not particular concrete classes like `Partner` and `Person`. I don't think this approach will work in that scenario.
Adam Robinson
@Rob: That's something I'd expect to be generic (or at least with one argument (the type)). ;) Though, I've never worked with Linq.
Bobby
A: 

To me it looks like a Partner is not a Person, but a Partner does have a Person. To me the interface would make sense as

var p = new Person();
var pa = new Partner(p);

pa.SomePartnerProperty = "...";
pa.Person.SomePersonProperty = "...";
Bob
I was expecting such an answer. :) This is the classic OOP solution to my design problem. But it is less than ideal, and I can explain why. When Partner becomes a property of Person ("has a"), this is a one-way relationship, which doesn't work well with terms of a particular domain, where each Partner is considered to _be_ a Person or a Company. It is impossible to have application deal with Partners and still be able to access the data of the Person beneath without some form of a back-reference. A custom type system solves this without "everything having to be a property of everything" mess.
aoven
Interesting... I see what you are trying to do. I can't think of a solution that I like which doesn't maintain a "back-reference"
Bob
A: 

For maintaining complete control over the conversion process (and ensuring that your code is actually run), your approach seems to be the most sensible. You can use explicit and implicit operators to control type conversion (either through implicit conversions or explicit via casting), but there are enough "gotchas" in those systems to make them a less-than-desirable choice for what it looks like you're attempting to architect.

Adam Robinson
A: 

For C# I think your 'first thought' (e.g. x.Is<T>()) looks fine (offhand I think it's what I would do).

If you go the F# route, you could use active patterns, e.g.

match p with
| IsPartner pa -> DoPartnerStuff(pa)
| _ -> JustPersonStuff(p)
Brian
While in the case of just two alternatives the F# pattern matching is just a fancy if/else, should the full model include multiple possible mutually exclusive roles, this approach gives you much a more "switch"-like style.
Steve Gilham
Nice. Using a language that offers greater syntax flexibility would sure solve the problem. :) Unfortunately, I already know that most consumers of this API will be members of the C# and VB croud.
aoven
A: 

just some online brainstorming...

class Role {
    public Role(Person p) { p.liRoles.Add(this); }
}
class Partner : Role {
    public Partner(Person p) : base(p) {}
}
class Person { 
    List<IRole> liRoles;
    public T As<T>() { 
        foreach(IRole r in liRoles){ if r is T return r; }
        return null;
    }
    public bool Is<T>() { return As<T>() != null; }
}
var p = new Person();
var pa = new Partner(p);

this should allow something like

if (p is Partner)
   (p as Partner).PartnerMethod();

I haven't run this through a compiler though, so it might be completely wrong ;)

devio
Thanks, but you missed the point. The problem is not the _implementation_ of IS and AS - I've got that covered just fine. What I'm looking for is the API _signature_ for IS and AS. IOW, how one uses them in app code. And in this regard, your suggestion yields the same effect as my original one. It also retains the same problem: i.e. the slight abuse of generic methods (the argument T in Is<T> is not really used in the signature), which is, naturally, picked up by FxCop.
aoven
A: 

Wait... I don't see what's wrong with normal OO code is (unless you have tons of class)

interface IPartner {...}
interface IPerson {...}
interface ICompany {...}

class Person : IPerson {...}
class PersonPartner : Person, IPartner {...}
class Company : ICompany {...}
class CompanyPartner : Company, IPartner {...}

Or course this would be easier with multiple inheritance...

tster
The actual hierarchies in my type system result in large numbers of possible combinations. Having to declare a class for every combination is not the desired outcome. If it was, there would be no need for a custom type system. :)
aoven
+1  A: 

My reading of what the question is after would be a mixin system for C# -- as discussed at http://stackoverflow.com/questions/255553/is-it-possible-to-implement-mixins-in-c. Bottom line is that there are some ways of getting most of what a proper mixin syntax would permit -- though personally I've never found these workrounds to give a good cost/benefit result.

Steve Gilham
And right you'd be! It's basically mixins, with some special constraints on valid compositions sprinkled on top. Thanks for the link - I somehow missed that thread.
aoven
A: 

I've been thinking a lot lately about these concepts, and about traits in C#. I think the idea that you have for the type test extension methods doesn't get much better than that (it's almost exactly what I have come up with). Borrowing some terminology from Perl 6 roles, I'd call your Is method Does instead. But I'd leave As unchanged.

But, I don't think that this usage feels natural for what you're trying to achieve:

var p = new Person(); 
var pa = new Partner(p); 

It doesn't look like they'll point to the same instance. Now borrowing from Scala, maybe something like this instead?

var p = new Person(); 
var pa = p.With<Partner>(); 

Anyway, did you finish your API? Is it open-source?

Jordão