Inheritable Default ValuesMaking systemic specialisation more declarative This feature was introduced in Axon 1.6.0 (Kamaelia 0.6.0)The idea behind this is to allow a more compact, declarative way of defining more complex Kamaelia systems. It stemmed initially from an observation that two of us wanted to do this: def ReusableSocketAddrServer(port=100, Specifically we noticed that we were creating a fair number of factory functions which only really differed based on on value. The problem we have here is that this is relatively fragile. Specifically, what happens if ServerCore adds in extra arguments - do we also update ReusableSocketAddrServer ? What if we don't, does someone else come along and duplicate our code in order to support those extra arguments? OK, well we can handle this in python if we use the **argd syntax. If we do that, we can do that this way: Whilst that's maybe more reflective of what we wanted to do, it now looks rather obscured. We then realised that there is a useful side effect of python namespaces that we can take advantage of, which is this:def ReusableSocketAddr(**argd):
Then we can do this:def __init__(self, **argd): This has a number of advantages over the factory method:class ReusableSocketAttrServer(ServerCore):
from import Kamaelia.Internet.TCPServer import TCPServerHypothetical File: ExamplePatch.py Replacing TCPServer here with our TracedTCPServer would have to look like this: The downside of this as well is that this is not particularly targetted, and leads to the situation where it would be more natural to create a copy of the code for traced versions. This misses one of the handy features of what inheritance gives us, which is controlled duplication of functionality with little twists of functionality. By comparison, with inheritable default values, we can do this instead:from Hypothetical import TracedTCPServerHypothetical File: ExamplePatchUser.py However, when someone wants to create a traced version they can be far more to the point. Suppose they have code that looks like this:from import Kamaelia.Internet.TCPServer import TCPServerHypothetical File: ExamplePatch.py ServerCore(port = 1500, protocol=WhizzyProto1).run() They can change it over to use the hypothetical TracedTCPServer like this: from Hypothetical import TracedTCPServer Not only that, but if they wanted to define this as a common thing they wanted to do, they could do this: Which would then get used:class TracedServerCore(ServerCore): Whilst this seems theoretical, it was bandied about as a possible idea for nearly a year until it suddenly became extremely useful - specifically in the greylisting code to allow it to use inactivity timers on connected sockets, as well as configuration of protocol handlers in a declarative manner:TracedServerCore(port = 1500, protocol=WhizzyProto1).run() Since then the idiom has been found to be useful in other scenarios.class GreylistServer(ServerCore): It's also worth noticing that this also means that the ServerCore code could be repurposed to work with servers that act in a similar way to TCPServer. For example, a hypothetical ConnectionBasedUDPListener, could be created which operated in a similar manner to TCPServer, and then reused as follows: Thereby making it as simple to create connection oriented UDP servers as it would be to create TCPServers. The only difference between the two being lack of guarantee of ordering or delivery.class UDPServerCore(ServerCore): Downsides?The clear downside of this is that the signature of your component's generally initialiser becomes this:This in turn puts a greater onus on you as a component writer to document the arguments to your component in a clearer manner.def __init__(self, **argd): But WHY???This is an implicit thing. In Kamaelia when syntactic sugar gets added (and that's precisely what this is), one of the most common aims is to aim to move towards a declarative reusable syntax. After all, if you consider that the starting point was this:You're actually starting off with something very fragile, especially considering that if ServerCore changes it's configuration, you have to change this factory function as well.def ReusableSocketAddrServer(port=100, Secondly, the next approach for dealing with changing __init__ialiser arguments is to use **argd, you then end up with something which is a bit perl-ish in structure, and obfuscates what's really going on: However, by switching over to an inheritable default value approach you gain something which is declarative, picks up new default values from the base class cleanly and makes it much clearer that actually this returns objects of this type, just preconfigured in a particular way:def ReusableSocketAddr(**argd): So, by aiming for a syntactic sugar that's declarative in nature, we're hopefully making the intent in the system clearer.class ReusableSocketAttrServer(ServerCore): SummaryIf you want to provide default values for parameters for your components, and please do, providing them in the form of inheritable default values will make your components more useful to others. You don't have to do this, and if you find it odd, simply don't do this. However if you do, it would be appreciated by the users of your code. |
Kamaelia
is an open source project originated from and guided by BBC
Research. For more information browse the site or get in
contact.
This is an ongoing community based development site. As a result the contents of this page is the opinions of the contributors of the pages involved not the organisations involved. Specificially, this page may contain personal views which are not the views of the BBC. (the site is powered by a wiki engine)
(C) Copyright 2008 Kamaelia Contributors, including the British Broadcasting Corporation, All Rights Reserved