Hi!
I hope the title doesn't sound too subjective; I absolutely do not mean to start a debate on OO in general. I'd merely like to discuss the basic pros and cons for different ways of solving the following sort of problem.
Let's take this minimal example: you want to express an abstract datatype T with functions that may take T as input, output, or both:
f1 : Takes a T, returns an int
f2 : Takes a string, returns a T
f3 : Takes a T and a double, returns another T
I'd like to avoid downcasting and any other dynamic typing. I'd also like to avoid mutation whenever possible.
1: Abstract-class-based attempt
abstract class T {
abstract int f1();
// We can't have abstract constructors, so the best we can do, as I see it, is:
abstract void f2(string s);
// The convention would be that you'd replace calls to the original f2 by invocation of the nullary constructor of the implementing type, followed by invocation of f2. f2 would need to have side-effects to be of any use.
// f3 is a problem too:
abstract T f3(double d);
// This doesn't express that the return value is of the *same* type as the object whose method is invoked; it just expresses that the return value is *some* T.
}
2: Parametric polymorphism and an auxilliary class
(all implementing classes of TImpl will be singleton classes):
abstract class TImpl<T> {
abstract int f1(T t);
abstract T f2(string s);
abstract T f3(T t, double d);
}
We no longer express that some concrete type actually implements our original spec -- an implementation is simply a type Foo for which we happen to have an instance of TImpl. This doesn't seem to be a problem: If you want a function that works on arbitrary implementations, you just do something like:
// Say we want to return a Bar given an arbitrary implementation of our abstract type
Bar bar<T>(TImpl<T> ti, T t);
At this point, one might as well skip inheritance and singletons altogether and use a
3 First-class function table
class /* or struct, even */ TDictT<T> {
readonly Func<T,int> f1;
readonly Func<string,T> f2;
readonly Func<T,double,T> f3;
TDict( ... ) {
this.f1 = f1;
this.f2 = f2;
this.f3 = f3;
}
}
Bar bar<T>(TDict<T> td; T t);
Though I don't see much practical difference between #2 and #3.
Example Implementation
class MyT {
/* raw data structure goes here; this class needn't have any methods */
}
// It doesn't matter where we put the following; could be a static method of MyT, or some static class collecting dictionaries
static readonly TDict<MyT> MyTDict
= new TDict<MyT>(
(t) => /* body of f1 goes here */ ,
// f2
(s) => /* body of f2 goes here */,
// f3
(t,d) => /* body of f3 goes here */
);
Thoughts? #3 is unidiomatic, but it seems rather safe and clean. One question is whether there are any performance concerns with it. I don't usually need dynamic dispatch, and I'd prefer if these function bodies get statically inlined in places where the concrete implementing type is known statically. Is #2 better in that regard?