Replies: 5 comments
-
This sounds like an incredibly useful feature! Your first point seems especially relevant to my situation. Probably my biggest issue with the current state of Loyc is that the rules governing I'm not entirely in agreement with regard to the caching implementation, though. I feel like the primary use case for this feature would be to have a different "view" of an But why bake (cached) properties of the view into the Here's what that would look like in practice: class TypeNode {
private TypeNode(LNode n) {
Name = n.Args[0];
// ...
}
public readonly LNode Name;
// ...
private static ConditionalWeakTable<LNode, TypeNode> views
= new ConditionalWeakTable<LNode, TypeNode>();
public static explicit operator TypeNode(LNode n) {
TypeNode r;
if (views.TryGetValue(n, out r)) return r;
r = new TypeNode(n);
if (!r.IsValid) throw new ArgumentException();
views[n] = r;
return r;
}
} One of the key advantages of this design is that it's very much a "pay for what you use" scheme. Views only cost memory when you actually use them. Also, this design interacts nicely with garbage collection: once you're done using a node, the garbage collector will just collect it as well as its views. The code's not incredibly pretty, but it shouldn't be too hard to write a macro for defining new views. IMO, the only real downside to this design is that values in a This is what a view based on class TypeNode {
public LNode Node { get; private set; }
private TypeNode(LNode n) { Node = n; }
public LNode Name { get { return Node.Args[0]; } }
public VList<LNode> BaseTypes { get { return Node.Args[1].Args; } }
public LNode Body { get { return Node.Args[2]; } }
public Symbol Kind { get { return Node.Name; } }
public bool IsValid { get { return EcsValidators.SpaceDefinitionKind(n) != null; } }
private static WeakCache<LNode, TypeNode> views
= new WeakCache<LNode, TypeNode>();
private static TypeNode CreateView(LNode n) {
return new TypeNode(n);
}
public static explicit operator TypeNode(LNode n) {
var r = views.Get(n, CreateView);
if (!r.IsValid) throw new ArgumentException();
return r;
}
} |
Beta Was this translation helpful? Give feedback.
-
Interesting, I did not know about The nice thing about your ideas is that they require no changes to I guess it's a question of how often people want to cache data. So I'd like to look at the memory cost. Let's say I optimize for up to two cached items. If, say, you want to cache one or two things on 25% of nodes... and How does this compare to an independent You avoid the cost of a cast, but you gain the cost of a full hashtable lookup (probably with a I also wonder if there's any valuable feature that would be more than just caching - associating a value permanently with a node, no deletions? Hmm, not sure that that's a good idea. |
Beta Was this translation helpful? Give feedback.
-
And let's keep |
Beta Was this translation helpful? Give feedback.
-
Hmmm. I don't fundamentally disagree with your analysis, but isn't it kind of biased in the sense that it takes into account the memory cost of a feature of my suggestion (weak references) that your original idea doesn't have? That's kind of comparing apples to oranges. Surely adding weak references to your solution changes the math? Permanently associating values with nodes would be... invasive. Especially if the values being associated are semantically relevant. That'd essentially add a third type of semantically relevant storage to |
Beta Was this translation helpful? Give feedback.
-
Oh right, I guess I should count only one weak reference (the keys, e.g. using |
Beta Was this translation helpful? Give feedback.
-
A Loyc tree node (
LNode
) typically represents some specific concept, such as a data type name or a class definition. I've been thinking for a long time that we needed some way to conveniently obtain access to convenient "wrapper" around anLNode
that would make it act like some specific type. Now I suspect it's worthwhile to go a step further and provide caching functionality.Here are two specific things that people might want to do:
One: validate a node as being of a specific type and get a wrapper that provides convenient access to it. For example, I could define a structure like this to encapsulate a class definition:
Okay, you can already do this today, and you could use LeMP to generate structures for many node types quickly. However, perhaps in some scenarios you would convert the same Loyc tree to a wrapper many times, requiring validation each time.
Two: compute something from an LNode. We could add caching functionality of some sort to Loyc trees for this. Since nodes are immutable, the idea would be to associate immutable data with the nodes.
My idea is to add some kind of
IDictionary<Identification,object>
to each node, with restricted access: the dictionary doesn't exist at first, it is created only when needed, and the values associated with eachIdentification
are computed automatically using some kind of global "registrar" or function table. What isIdentification
? I'm uncertain; it might just beSymbol
. Anyway, if you have anLNode N
thenN[Id]
gets theobject
associated withId
. If there is no object associated withId
,LNode
will look up the helper functionh
associated withId
in the registrar and callh(N)
.h
returns a value or object that is returned fromN[Id]
, and also cached, so that if you callN[Id]
again, you get the same object.At any time it should be possible to discard all cached info, even recursively, e.g.
N.ClearCache()
.So, let's say you're implementing a Java compiler with Loyc trees. You might create a series of
Identification
objects, one for each kind of node in Java:Node.Package
,Node.Expr
,Node.ForLoop
etc. Then you can callN[Node.ForLoop]
to obtain the "while loop" representation of the node.Ahh, but it's an
object
- no type information. Can we do something about that? Well, I don't think it's possible to avoid a typecast, but maybe we can make the cast more convenient. First of all, I can't recall at this moment whethernode<ForLoop>[Node.ForLoop]
is legal C# syntax, but if so, we can allow that. But it would be better if we could allownode<ForLoop>[]
, or if that's not legal syntax,node.Get<ForLoop>()
(yuck). One way to do this would be to auto-generate anIdentification
object for each type as it is requested. Then,LNode
could use reflection to find a static functionForLoop.From(LNode)
function which would be used to obtain theForLoop
object. However, it would be nice to avoid reflection because sometimes .NET AOT compilation doesn't support it.Then there's the matter of efficiency. Sometimes it is cheap to compute an associated value for a node; other times it is expensive. Sometimes the expense depends on the node (this is an issue for computing hashcodes - hashing an identifier node is trivial, hashing a large class is not.) It would be nice to have some kind of mechanism to avoid caching things that are cheap to compute, particularly if it means avoiding the creation of a relatively expensive dictionary of cached things. So, instead of simply returning a value, the helper function
h
could return two values: one object to be returned and one integer representing how willingLNode
should be to cache the value (typically proportional to how expensive it was to compute the value or validate that the node syntax was correct). The caching strategy could be user-customizable, within reason... changing strategies should not affect nodes that already exist and have caches.The other element of efficiency is memory usage. A likely optimization is to avoid allocating an entire dictionary when there is only one cached value. In any case, this feature would require adding 1 or 2 words (references) to every
LNode
.So @jonathanvdc, does this sound like a useful feature? Is it worth a cost in memory-per-node to support the mere possibility of it?
Beta Was this translation helpful? Give feedback.
All reactions