[cap-talk] small notes re: waterken
William ML Leslie
william.leslie.ttg at gmail.com
Sat Mar 5 05:25:29 PST 2011
On 5 March 2011 01:30, Rob Meijer <capibara at xs4all.nl> wrote:
> I distinguish that between __del__ and finaly for the reason that finaly
> often braiks the principle of least knowledge while __del__ does not, and
> for the reason that finaly spreads responsibility for a resource all the
> way up the tree from where it is being initially acquired, thus 'finally'
> in fact brakes encapsulation principles. Let me give a simple example.
> Lets say Alice acquires a new Bob and a new Carol. Now Bob acquires a Dave
> and Carol acquires an Eve. Now Dave acquires a resource lets say a lock.
> Alice invokes a method of Carol who invokes a method of Eve and this last
> method throws an exception.
> With __del__, when the exception travels up, the exception will let Bob go
> out of scope, bob gets destroyed, its member Dave with it. Dave's
> destructo gets called and the lock will get released promptly.
> Now look at the finaly solution. Dave will need a 'release' method so that
> Bob can ask it to release its lock resource (should Dave know there is a
> lock resource?). Bob will also need a 'release' method so that Alice can
> ask Bob to ask Dave to release the lock. Now with that in place, Alice can
> invoke Bob.release() in its finaly for invocation of Carol.somemethod().
> This basicaly means two things to me. Alice and Bob become responsible for
> resource management of a resource held by Dave. This brakes the principle
> of least knowledge and makes the whole system brittle by spreading
> responsibility and breaking encapsulation.
> I hope the above example clarifies my argument.
And yet, tying resource management to object lifetime (in the
refcounting or gc sense) is potentially confusing* and composes
awkwardly. How does Alice know that she is using this resource and
must not cache Bob? Is she expected to do that "DisTasteful =
Nothing" that you frequently see in VB code before calling other
methods that may require a resource? Idiomatically?
If Dave cannot acquire the lock as a with statement because it is
expected to be held across several calls, the appropriate interface
would be to return a closable that the caller invokes those methods
within context of.
For those situations where this sort of pattern is not possible, I
would like to have an interface to consistently access either
dynamically scoped (in the 'with' sense) or transaction scoped objects
that assist in lifecycle management of such resources. While this
approach still has some composition related overhead (you must
explicitly create a new lifecycle context when Alice is to be passed a
closure by Bob in the above example, with which he tags resources
associated with the return value, etc), it means that the programmer
is not constrained by an implicit stack discipline (inflexible B&D) or
a ref-counting algorithm (fragile and difficult to reason about). It
puts lifecycle management in the hands of the programmers.
On the other hand, that idea seems to share some symmetry with
explicit region annotation, applied to abstract resources rather than
just memory. Hard to say if it would feel just similarly complicated.
* I believe it was Tim Peters who said that __del__ is responsible for
burning more brain cells than every other python language feature
combined (including descriptors and metaclasses). From my (possibly
very biased!) circle; weakref callbacks seem to be the generally
preferred interface over __del__. Because they provide fewer and
simpler guarantees, you are forced to understand what you are doing.
Much like the difference between threads and processes or asynchronous
message sends. Where asynchronous sends force you to structure your
application, weakref callbacks force you to think about what
conditions need to be preserved when your finaliser is called.
More information about the cap-talk