I am back on track to create the next generation user interface framework. Inspired by my newest gadget the IPod touch, usability gets – again – a high priority in my private research.
This blog entry is about functional composition, which I recently found a very important feature to be added to my C# development box. Requirements for a functional composition are closures and a lot of syntatic sugar that makes declaring functions fun.
Beginning with C# 3, it’s all there: type inference and writing delegates in the concise lambda notation is starting to get into mainstream programming :)
To enable functional composition, classes need to be restructured a little. Instead of specifying a class with abstract member functions (or an interface), they need to be declared to use functions (delegates in C#). So for example:
abstract class InteractiveEvents { public abstract void onMouseMove(Point pos); public abstract void onLeftMouseButtonDown(Point pos); public abstract void onLeftMouseButtonUp(Point pos); };
you may want to write
sealed class InteractiveEvents { public Action<Point> onMouseMove; public Action<Point> onLeftMouseButtonDown; public Action<Point> onLeftMouseButtonUp; };
For functions with return values, use the Func<> generic delegate.
Now, to create new functionality, you don’t need to derive from Event anymore, you may either replace the functions bound to the field members or – to create a prototype that behaves like the original – use the Object.MemberwiseClone() method.
MemberwiseClone() is protected, so to make it accessible, a base class needs to be used:
abstract class Cloneable<T> where T : class { public T clone() { return MemberwiseClone() as T; } };
sealed class InteractiveEvents : Cloneable<InteractiveEvents> { ... };
So, for example, to compose a new InteractiveEvents instance that filters all mouse move events, but otherwise behaves exactly like the original, overwriting one simple method is sufficient:
InteractiveEvents suppressMouseMove(InteractiveEvents original) { var ieNew = original.clone(); ieNew.onMouseMove = (p) => {}; return ieNew; }
Complex behavior, including states is possible, too. The following modification calls an other listener when the mouse has been moved while the left button is pressed.
InteractiveEvents trackLeftButtonMoves(InteractiveEvents original, Action<Point> listener) { // The "tracking", "original" and "listener" variables are bound to the delegates declared below // Instead placing them on the stack, the C# compiler will create a separate heap based instance for them bool tracking = false; var copy = original.clone(); copy.onLeftMouseButtonDown = (p) => { tracking = true; original.onLeftMouseButtonDown(p); }; copy.onLeftMouseButtonUp = (p) => { tracking = false; original.onLeftMouseButtonUp(p); }; copy.onMouseMove = (p) => { if (tracking) listener(p); original.onMouseMove(p); }; return copy; };
And finally, it’s all about functions…
yours
armin
Update: In the process of porting an older layout library using functional composition, I found it more convenient to put smaller collections of functions into structs. This avoids the hack that uses the MemberwiseClone() method. So the above example could be rewritten as:
struct InteractiveEvents { public Action<Point> onMouseMove; public Action<Point> onLeftMouseButtonDown; public Action<Point> onLeftMouseButtonUp; };
Copying is then established by a simple assignment:
var copy = original;
Comments
Bubble up, bubble down
came to your blog based on the PEG parser search…
anyways saw this post can you support bubble up and bubble down features as in routed events for wpf?
PreviewMouseDown
MouseDown
i guess the implementation could be created using a double linked list but i haven’t actually implemented… would like to see it done using this syntax you have going on here.