Hi Steve,

I've been away - as I mentioned on private mail - and getting back up to 
speed. I did get a chance to read your mails whilst away, but not to
respond The key thing I've taken away from your comments is this:

> ... I have to tell you I'm really struggling
> with finding peace with the "Inheritable Default Values" approach.

... which I think is the strongest objection you had stylistically. 

I think you get why I like the idea, and suspect you may also like this (esp 
when you see the alternatives below...) :

class GreylistServer(ServerCore):
    logfile = config["greylist_log"]
    debuglogfile = config["greylist_debuglog"]
    socketOptions=(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    port = config["port"]
    class TCPS(TCPServer):
        CSA = NoActivityTimeout(ConnectedSocketAdapter,
                                         timeout=config["inactivity_timeout"],
                                         debug=False)
    class protocol(GreyListingPolicy):
        servername = config["servername"]
        serverid = config["serverid"]
        smtp_ip = config["smtp_ip"]
        smtp_port = config["smtp_port"]
        allowed_senders = config["allowed_senders"]
        allowed_sender_nets = config["allowed_sender_nets"]
        allowed_domains = config["allowed_domains"]
        whitelisted_triples = config["whitelisted_triples"]
        whitelisted_nonstandard_triples = \
                        config["whitelisted_nonstandard_triples"]

... since it makes configuration/monkey patching a default, and structured 
safely, and means you can end up writing less code. 

BUT, the technique taken to get there has a cost you feel is too high. Which 
is as you note a position I have some sympathy with.

> To boil it down, I can see this approach useful for optional
> parameters with defaults, but I think it sacrifices too much clarity
> for negligible gain when used for fundamentally necessary parameters.

You also won't be alone here, so I've had a think. (being away from being
able to reply has probably been helpful here, since I think I've reached a
similar conclusion to you)

Before I come to that, it's probably useful to reply to this:

> That said, it seems you came to a different internal equilibrium and I
> would really appreciate it if you could help me see it from your
> perspective.

... which takes us down the rabbit hole.

The idea was really to try it and see how it worked, what the practical 
objections are, or whether doing things this way was nice.

I'll start off with TCPClient first, since it has to connect to something,
somewhere - otherwise it's not a generic TCPClient. It's also useful
because it hasn't currently been rewritten to taken advantage of
inheritable default values.

[ I'm not going to use the SingleTick example here because I don't
  personally agree that you can't have a valid default delay, and I don't
  want to get into that here :), and the views will cross over either way ]

TCPClient's init method currently looks like this:

class TCPClient(Axon.Component.component):
   def __init__(self,host,port,delay=0, connect_timeout=60):
      super(TCPClient, self).__init__()
      self.host = host
      self.port = port
      self.delay=delay
      self.CSA = None
      self.sock = None
      self.howDied = None
      self.connect_timeout = connect_timeout

Whereas with inheritable defaults, for everything, it could be changed to:

class TCPClient(Axon.Component.component):
   host = None
   port = None
   delay = 0
   connect_timeout = 60
   def __init__(self,**kwargs):
      super(TCPClient, self).__init__(**kwargs)
      self.CSA = None
      self.sock = None
      self.howDied = None
      if self.host is None:
          raise TypeError("__init__ requires a host argument")
      if self.port is None:
          raise TypeError("__init__ requires a port argument")

This then allows this:

class Port80Client_Declarative(TCPClient):
   port = 80

class KamaeliaWebsiteClient_Declarative(Port80Client):
   host = "www.kamaelia.org"

.. both without change to the __init__ structure. ie purely declarative 
changes.

The alternatives are to create new factories explicitly like this:

def Port80Client_Factory(host, **args):
    return TCPClient(host, 80, **args)

def KamaeliaWebsiteClient_Factory(**args):
    return Port80Client("www.kamaelia.org", **args)

( I'm not recommending these BTW, just showing thought process. )

On a single level the rationale is less clear - Factory/Declarative are much
of a muchness. The issue is what happens when you go deeper inside
components that form larger systems. (ie something more "real")

In that scenario, greylister is probably the best example here:

class GreylistServer_DeclarativeVersion(ServerCore):
    logfile = config["greylist_log"]
    debuglogfile = config["greylist_debuglog"]
#    socketOptions=(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    port = config["port"]
    class TCPS(TCPServer):
        CSA = NoActivityTimeout(ConnectedSocketAdapter,
                                         timeout=config["inactivity_timeout"],
                                         debug=False)
    class protocol(GreyListingPolicy):
        servername = config["servername"]
        serverid = config["serverid"]
        smtp_ip = config["smtp_ip"]
        ...

You can change this to the other form, but suppose it looks more like this:

def GreylistServer_FactoryVersion(config):
    def customTCPServerFactory():
        return TCPServer(
                CSA = NoActivityTimeout(ConnectedSocketAdapter,
                                         timeout=config["inactivity_timeout"],
                                         debug=False)
                 )
    def protocolFactory():
         return GreyListingPolicy(
                           servername = config["servername"],
                           serverid = config["serverid"],
                           smtp_ip = config["smtp_ip"],
                  )

    return ServerCore( logfile = config["greylist_log"],
                              debuglogfile = config["greylist_debuglog"],
#                              socketOptions=(socket.SOL_SOCKET, ..., 1),
                              port = config["port"],
                              TCPS = customTCPServerFactory,
                              protocol = protocolFactory)

Personally, I find that rather more convoluted - and also more fragile. The 
latter version is very explicit about how things are made, but it's less 
clear what happens if you, as a user of the Greylister adapt it. 

I've deliberately commented out socketOptions in both of the examples above. 
Now suppose in the former based entirely around inheritable defaults you want 
to change this:

class GreylistServer_DeclarativeVersion(ServerCore):
    .... as before ...

To support changing socket options, how do you do it? You 'just' do this:

class MyGreylistServer(GreylistServer_DeclarativeVersion):
    socketOptions=(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

In the latter version, you either have to
   * Copy all the code, from GreylistServer_FactoryVersion
   * Change GreylistServer_FactoryVersion such that it supports this extra
      option.

Which is less appealing - to me at any rate :)

You can also argue however, that the declarative approach is more inline with 
the open closed principle of "Software entities (classes, modules, functions, 
etc.) should be open for extension, but closed for modification."

With the factory approach, you actually have to take a copy of the code and 
modify the copy, whereas with the declarative (inheritable defaults) approach 
you can continue to extend the system, without modifying the original.

More subtley it also plays into the fact that the higher level you do in 
Axon/Kamaelia, more of it is declarative, whereas as you go down inside 
components they're OO/procedural.

Which is all well and good, BUT... practicality beats purity...

As you say here, this is relatively awkward if the most common case is going 
to be simply using the TCPClient component, rather than inheriting from it... 

And it is the most common case of course.

So this led me to this alternative set of possibilities:

class TCPClient(Axon.Component.component):
   delay = 0
   connect_timeout = 60
   def __init__(self, host, port, **kwargs):
      super(TCPClient, self).__init__(**kwargs)
      self.CSA = None
      self.sock = None
      self.howDied = None
      self.host = host
      self.port = port

class Port80Client_Declarative(TCPClient):
   port = 80
   def __init__(self, host, **kwargs):
      port = kwargs.pop("port", self.port)
      super(Port80Client_Declarative, self).__init__(host, port, **kwargs)

class KamaeliaWebsiteClient_Declarative(Port80Client):
   host = "www.kamaelia.org"
   def __init__(self, **kwargs):
      host = kwargs.pop("host", self.host)
      super(KamaeliaWebsiteClient_Declarative, self).__init__(host, **kwargs)
      ...

... as being the most sensible alternative.

The only downside of all this really is that it prevents this:
   TCPClient(host="www.kamaelia.org", port="80")

... which is nice, but not necessarily much of a loss. 

I think based on this, most of my thoughts here are pretty compatible with the 
changes you've made, but I'll have a detailed look tonight.

> but I'd like you to take a look at this way first.  

I have indeed, and as you'll see I've taken your thoughts on board, and I 
think reached a similar conclusion :-)

Apologies for the delay, but I really did need a holiday ;-) :-)

Regards,


Michael.
-- 
http://yeoldeclue.com/blog
http://twitter.com/kamaelian
http://www.kamaelia.org/Home

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"kamaelia" group.
To post to this group, send email to kamaelia@googlegroups.com
To unsubscribe from this group, send email to 
kamaelia+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/kamaelia?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to