>
> Oh my. I do apologize for distressing you.
>
There is no need that you apologize to me. In fact I am the one who has
been rude.
To me having most basic data element that can't be instantiated on its own
is a big issue. While on the other side you think it is not a big deal.
In the comment attached to context ivar it is stated:
self.context = context # The context containing context.hiddenRootNode.
# Required so we can compute top-level siblings.
# It is named .context rather than .c to emphasize its limited
usage.
However if we examine the VNode class, we would find that c.hiddenRootNode
has been mentioned only once: in method findAllPotentiallyDirtyNodes where
it is used only to be excluded from the results of this method.
Vitalije: > [The VNode class] also knows indirectly about
> c.fileCommands.gnxDict.
>
> Edward: To my knowledge, this is not true. The VNode class does not in
> any way become entangled with other classes merely because v.context
> exists! The VNode class knows *nothing* about how other classes use gnx's.
>
>
OTOH Let's try to instantiate VNode for the sake of testing, using the mock
object for context:
import leo.core.leoNodes as leoNodes
class DummyC:
def __init__(self):
self.hiddenRootNode = None
dc = DummyC()
v = leoNodes.VNode(dc, gnx="dewrwer.213213432244")
g.es('ok')
Executing this script we get an error:
AttributeError: 'DummyC' object has no attribute 'fileCommands'
If we add dummy fileCommands and try again, it will report that dummy
fileCommands has no attribute 'gnxDict'.
Why I think that this is not a small issue? First it makes testing much
harder than it can be. The other reason is that it can be relatively easily
avoided. And third it can cause hard to understand bugs.
Let me explain about bugs. There are some places in Leo code where the
execution path depends on whether we have a brand new vnode or vnode which
is already known (i.e. clone). And test to distinguish those two cases is
the presence or absence of the node in c.fileCommands.gnxDict. In some
cases it doesn't matter if the node is already attached to the main tree or
not. But in some cases it matters. Just instantiating a vnode creates the
node instance which is present in gnxDict, but is not part of the tree yet.
This can lead to mysterious bugs, that is hard to spot.
There are cases when one might want to have vnode with perhaps some
children and grandchildren to keep around in detached state. For example
settings whose values are subtrees. Also when building and transforming
tree it is sometimes useful to build it outside of the main tree without
disturbing gnxDict.
It also seem to me that the relation between commander and vnode violates
logic. There should be no commander without hiddenRootNode and yet node
can't be created without commander instance. That causes complications in
initialization process which is complex enough on its own.
Now, how this situation could be avoided:
from collections import defaultdict
class VNode(object):
__pool = defaultdict(dict)
def __init__(self, context, gnx=None):
# in this case context can be just a string for example c.hash()
# for backward compatibility
if isinstance(context, Commander):
context = context.hash()
# issue a deprecation warning
...
if gnx is None:
gnx = make_new_gnx(context)
if context:
# passing context = None prevents pool polution
VNode.__pool[context][gnx] = self
@staticmethod
def getGnxDict(ctx):
return VNode.__pool[ctx]
class FileCommands(object):
....
@property
def gnxDict(self):
return VNode.getGnxDict(self.c.hash())
This is backward compatible. All scripts that use c.fileCommands.gnxDict
can continue to work. However if one needs to build temporary tree of nodes
it can be done cleanly and easily.
Now, copying outline can be a method defined on vnode:
# inside VNode class definition
def to_list(self):
ua = None
if self.u:
ua = base64.encode(pickle.dumps(self.u))
return [self.gnx, self.h, self.b, ua, self.statusBits,
[v.to_list() for v in self.children]]
def copyOutline(self, set_clipboard):
set_clipboard(json.dumps(self.to_list()))
Pasting can be also method of vnode as I have demonstrated it in the
prototype recently.
Both, copyOutline and pasteOutline would be completely self contained and
the way they work will be completely hidden from the world outside VNode
class.
There would be no need for vnode to know anything about classes that are
higher up in hierarchy. VNode would be basic level class, that could be
used everywhere and anytime without introducing any new inter-dependency
with other modules.
We can go even further if we like.
The idea behind gnxDict (as I understand it) is to prevent the possibility
to have two Vnode instances with the same gnx. This can be achieved using
fly pattern. VNode could keep just a gnx as own ivar, and the rest ivars
become properties which keep their values in a private static dict whose
keys would be gnx and values could be lists [h, b, u, statusBits, children,
parents]
For example v.b would be:
# inside VNode class definition
def _setB(self, b):
VNode.__pool[self.context][self.gnx][1] = b
def _getB(self):
return VNode.__pool[self.context][self.gnx][1]
b = property(_getB, _setB)
and the rest of ivars would be implemented the same way.
In that case it would be enough for vnodes to have the same gnx and that
would automatically made them clones. No other class should know about pool
and gnxDict. If they know gnx they can instantiate new vnode with that gnx
wherever and whenever and the node will automatically have all fields as
any other node instance with the same gnx. Children and parents lists could
contain gnx-es instead of vnode instances.
This may seem a bit odd at first and that it would be slow. Performance
could be experimentally checked. I don't expect this to be much slower
especially in python >= 3.5. OTOH, having ability to check equality of two
vnodes just by looking in their gnx would allow many other improvements in
code. Making connections between nodes, i.e. linking outline can be
supervised by the VNode class. It means we would have 100% safe update
signals, much easier calculations of changes for the undo/redo code. This
capabilities would allow us to simplify undo/redo and also c.selectPosition
and of course incremental visual tree updates.
Prototypes that I have made in coffeescript and clojurescript prove this to
be very efficient way of displaying tree, making tree selection and updates
of body and headline.
Even if these are too big changes to be accepted or even discussed, the
first part regarding breaking dependency of VNode to Commander could be
done without too much risk or effort, and that alone would allow other
parts of code to be simplified. Also it would make VNode class testable
(more testable).
I hope that these code examples are more clear than my previous code
critics.
Vitalije
--
You received this message because you are subscribed to the Google Groups
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.