Suppose we have classes A and B, functions int fa(A&) and int fb(B&), and a class env with public instance members
struct env
{
A a;
B b;
}
Define "evaluation of fa or fb in the environment provided by env x" to mean f(x.a) when f is fa and f(x.b) when f is fb.
Problem: create a type which is able to hold references to fa or fb and, given an instance of env, is able to evaluate the held function.
#include <string> #include <iostream> using namespace std; // Setup for the problem. struct A { int member; }; struct B { std::string member; }; struct env { A a; B b; }; int fa(A& a) { cout << "fa called on an A with member = " << a.member << endl; return a.member; } int fb(B& b) { cout << "fb called on a B with member = " << b.member << endl; return int(b.member.length()); } // First define a generic way to extract the member of type T from // an instance of env. Note that this is non-intrusive: whatever // the definition of env is, if it is possible to get an A and a B // from it, the specialisations of tselect can do so. template <class T> struct tselect { }; template <> struct tselect<A> { static A& member(env& x) { return x.a; } }; template <> struct tselect<B> { static B& member(env& x) { return x.b; } }; // Check tselect performs as intended.. void test0(env& x) { cout << "a is " << tselect<A>::member(x).member << endl; cout << "b is " << tselect<B>::member(x).member << endl; } // Interface for a held function. class FuncHolder { public: virtual ~FuncHolder() {} virtual int invokeInEnv(env& x) const = 0; }; // Generic implementation. This scales well: // if we later want to add C,D,E and F we don't // need to write any new code here. template <class T> class FuncHolderImpl : public FuncHolder { public: typedef int (*funcT)(T&); funcT func_; FuncHolderImpl(funcT func) : func_(func) {} virtual int invokeInEnv(env& x) const { return func_(tselect<T>::member(x)); } }; // Convenience function for creating FuncHolders. // In the event that fa or fb were overloaded we'd // have to fall back on explicitly choosing the // type of the implementation class to construct. template <class T> FuncHolder* makeNewHolder(int (*func)(T&)) { return new FuncHolderImpl<T>(func); } // And here it is.. int main() { env e; e.a.member = 1; e.b.member = "hello"; test0(e); FuncHolder* fha = makeNewHolder(&fa); FuncHolder* fhb = makeNewHolder(&fb); fha->invokeInEnv(e); fhb->invokeInEnv(e); return 0; }
When compiled and run this produces
a is 1
b is hello
fa called on an A with member = 1
fb called on a B with member = hello
Templates keep track of all the type information. They know that fa needs an A and the tselect template knows how to go off and find one.
How about Python, a language in which templates are not needed because there are no static types to worry about? We need to give it just a tiny bit of help because fa can't tell us it needs an A: it doesn't know itself.
class A: def __init__(self, m): self.m = m class B: def __init__(self, n): self.n = n def fa(a): print 'fa called on a with a.m = ', a.m def fb(b): print 'fb called on b with b.n = ', b.n class env: def __init__(self, a, b): self.a = a self.b = b class FuncHolder: def __init__(self, func, argTypeKey): self.func = func self.argTypeKey = argTypeKey def invokeInEnv(self, e): return self.func(e.__dict__[self.argTypeKey]) fha = FuncHolder(fa, 'a') fhb = FuncHolder(fb, 'b') e = env(A(1), B('hello')) fha.invokeInEnv(e) fhb.invokeInEnv(e)
No doubt there are ways to make this cleaner and slicker but even as it stands it's usable and fairly concise. We give the FuncHolder a key telling it what type the function it is holding requires as an argument.
So what goes wrong when I try to write this in Scala using type parametrization? Firstly, because of type erasure, a generic FuncHolderImpl[T] can't decide what to do on the basis of the type of T because this is not known (inside the object). The only way it can change what it does is if it has an object of type T or related to it: then it can call an overridden member of some base of T to get it to do the work that varies. So we have to give it some kind of type marker (or at least that's what seemed like a possible solution).
But then we run into the second problem. Nowhere can we write code like this:
if (...) f(e.a) else f(e.b)
One branch or the other won't typecheck. Making a common base doesn't work unless, for each and every function, we write a second version which takes the base but expects the right subclass and casts to it (via pattern matching).
I've tried all sorts of other permutations but for some reason which I can't quite put my finger on, this seems to be hard to do. Suggested solutions in Scala or Java are most welcome.
No comments:
Post a Comment