I am attempting to write two versions of the same program:
- a performant version; and
- a slower version that lets the user know what's happening.
I imagine it's not entirely disimilar to how an IDE might implement a normal/debug mode.
My requirements, in decreasing order of importance, are as follows:
- the slow version should produce the same results as the performant version;
- the slow version should wrap a subset of public function calls made by the performant version;
- the requirement for the slower version should not adversely effect the performance of the performant version;
- preferably no code reproduction, but automated reproduction where necessary;
- minimal increase in code-base size; and
- ideally the slow version should be able to be packaged separately (presumably with a one-way dependence on the performant version)
I understand requirement 6 may be impossible, since requirement 2 requires access to a classes implementation details (for cases where a public function calls another public function).
For the sake of discussion, consider the following performant version of a program to tell a simple story.
class StoryTeller{
void tellBeginning() => print('This story involves many characters.');
void tellMiddle() => print('After a while, the plot thickens.');
void tellEnd() => print('The characters resolve their issues.');
void tellStory(){
tellBeginning();
tellMiddle();
tellEnd();
}
}
A naive implementation with mirrors such as the following:
class Wrapper{
_wrap(Function f, Symbol s){
var name = MirrorSystem.getName(s);
print('Entering $name');
var result = f();
print('Leaving $name');
return result;
}
}
@proxy
class StoryTellerProxy extends Wrapper implements StoryTeller{
final InstanceMirror mirror;
StoryTellerProxy(StoryTeller storyTeller): mirror = reflect(storyTeller);
@override
noSuchMethod(Invocation invocation) =>
_wrap(() => mirror.delegate(invocation), invocation.memberName);
}
I love the elegance of this solution, since I can change the interface of the performant version and this just works. Unfortunately, it fails to satisfy requirement 2, since the inner calls of tellStory() are not wrapped.
A simple though more verbose solution exists:
class StoryTellerVerbose extends StoryTeller with Wrapper{
void tellBeginning() => _wrap(() => super.tellBeginning(), #tellBeginning);
void tellMiddle() => _wrap(() => super.tellMiddle(), #tellMiddle);
void tellEnd() => _wrap(() => super.tellEnd(), #tellEnd);
void tellStory() => _wrap(() => super.tellStory(), #tellStory);
}
This code can easily be auto-generated using mirrors, but it can result in a large increase in the code-base size, particularly if the performant version has an extensive class hierarchy and I want to have a const analogue to const variables of a class deep in the class tree.
Also, if any class doesn't have a public constructor, this approach prevents the separation of the packages (I think).
I've also considered wrapping all methods of the base class with a wrap method, with the performant version having a trivial wrap function. However, I'm worried this will adversely effect the performant version's performance, particularly if the wrap method was to require, say, an invocation as an input. I also dislike the fact that this intrinsicly links my performant version to the slow version. In my head, I'm thinking there must be a way to make the slower version an extension of the performant version, rather than both versions being an extension of some more general super-version.
Am I missing something really obvious? Is there an in-built 'anySuchMethod' or some such? I'm hoping to combine the elegance of the proxy solution with the completeness of the verbose solution.