Shapes and More Shapes
Even abstract topology can be messy.
In weave, we have entities which are defined by three numbers: an id, src, and tgt. We've previously shown how the choice of source and target redefines the shape of the entity, allowing for building different structures and patterns of behaviors. But what if I told you that they introduce a whole new class of possible shapes, specifically stable self-recursive structures?
Let’s start with a familiar duo:
a = a → a,
b = a → b
We have one knot and one tether growing off of it. If we now mutate the knot’s target…
tgt(a) = b
we get a pretty unique thing:
a = a → b,
b = a → b.
I’ve chosen this one to be the woven tools logo because it was just that cool. These two entities are now codependent and have the same source and target as each other. For all intents and purposes, a mark and a tether shouldn’t be able to link in such a way, and yet here we are!
So what is this? I don’t honestly know, but it’s a good basis for a linked-list, with the new elements inserted as marks at the end of the red arrow and pointing towards b, while the blue arrow remains a quick way to reach the end of the list. This might just be my style but I’d also like one external arrow L = a → b, so that we’d have a singular entry point to this structure. Passing L around is easy, its source is the sentinel at the start of the list, and its target the sentinel at the end.
We can similarly turn the source around using src(a) = b, creating this:
There’s a cool space for shapes like these for dealing with equality classes, I think, although I yet need to explore it more thoroughly.
The original four shapes weave introduced — knot, arrow, mark, and tether — are called motifs. Motifs lived alone for a really long time. Years, in fact. These stable self-recursive structures are only days old in comparison, and I don't even know how many there are, in comparison. They seem super useful even in their infancy and I asked my friend, Darinka Zobenica, who I keep pestering with these things, to come up with a name for these self-referential thingies she noticed. She came up with mints and it stuck! I think mints are a stroke or genius on her part and pure good luck on mine, to have bothered her with this topic.
A much greater wealth of shape
Mints are an indicator, for me. A canary in a coal mine, the imprint of the rigidity of my own baby steps into this domain. The combinatorics of identity, source, and target delivered motifs and in isolation, these are the building blocks of these identity relations. However, in many cases, the minimal form of a pattern doesn't deal with the nothingness that a knot represents. I almost feel that knots, even though we're used to thinking of them as graph nodes, are just stepping stones or prototype-entities that can be there to give us some time and material to mold them into something more useful.
There’s a strange calculus behind what’s “useful” in weave, however. For one, there’s a certain “goodness” in direct source-to-target traversals. What I mean by this is more visible in an image. Compare these two scenarios:
Above, we have a standard knot-arrow-knot system we’re used to for making graphs. If we want to traverse this starting from the left, we can’t just follow a to its target — the target of a knot is itself! So the traversal of the above graph would be stuck there on the left for a really long time. To get to the arrow, we need to go outside the a entity and into the storage of sources and targets, looking for any entity t such that src(t) = a. This isn’t a huge operation if implemented as it is in weave - we’re using a bit of extra space to keep every dependency hashed, so that we don’t have to go through the whole storage, but it still feels like a chore.
Compare it to the second scenario: if we trace x to its target, we end up with y. if we trace that to its target, we end up with z, and are now stuck here. We’ve just traversed the whole thing left to right. This lovely power of transitivite flow is only lessened by the fact that you need to keep it simple with structures like this: because the flow is a part of the definition, each and every one of these pieces is uniquely and fully defined. We can’t attach new arrows here, we can’t have multiple paths. It almost seems inviting: maybe we shouldn’t be looking at weave at this low-level but rather one step above where the shape will automatically optimize itself if it recognizes there’s no need to do a more demanding operation, and deoptimize itself in case one is needed. There might be an optimal usage for knots outside of proto-entities, but given that mints are a good replacement that guarantees cascading deletion, I think they won’t show up much except for boundary objects - objects that are to be sent across especially agressive boundaries that don’t share structure well (between languages, processes, etc.)
Scenario two here is very interesting because it’s similarly self-referential as the mints above, but looks more “normal”. Both endpoints depend on the arrow in at least one of their endpoints, and the arrow uses both of them. To construct this, we have to go with:
x = x → x
z = z → z
y = x → z
tgt(x) = y
src(z) = y
In a way, this mutation is similar to lazy evaluation, which might be a larger and more important topic to talk about: another way to do this is to declare but lazily define pieces of the structure, like so:
exists x, z
y = x → z
x = x → y
z = y → z
If a language a bit higher up than weave would want to implement this, it would be trivial: simply make knots and allow full redefinitions to reuse the same entities. There’s probably a lot of material about the connection and possible equivalence between mutability and laziness, given that they’re opposing forces in imperative and functional languages, both of which are of the same expressive strength. I find it interesting that denying both of these makes a whole set of interesting and seemingly important structures be non-constructable… More research required.
How about bidirectional arrows?
Having mints that look like inwards made me think about the opposite: could we have something that closely resembles a bidirectional arrow? We could ever interpret it as a non-directed edge! This is a case where the math checks out, but I’m having a very hard time drawing this peculiar mint. Here’s my thinking:
Given some two entities a and b (we won’t ask that they be knots or something else), we start off making marks towards both of them:
m = m → a
n = n → b
Now, we change the sources of both arrows with the other one:
src(n) = m
src(m) = n
The current state of things speaks of two arrows, m = n → a and n = m → b, and this is, for sure, a mint, and one I can’t seem to draw… maybe something like this?
It still feels off, but it works. If you want to go from one end to the other, the action is as follows: if you start from an entity X and execute tgt(src(arrows_in(X))), you’ll end up on the other end! Here’s a sanity check:
tgt(src(arrows_in(a)))
= tgt(src([m]))
= tgt([n])
= [b]
tgt(src(arrows_in(b)))
= tgt(src([n]))
= tgt([m])
= [a]
Don’t mind the array-notation, weave explicitly tries to always expect that functions might return a whole array of things. This makes it easy to do closures, but also puts healthy pressure on the user to choose their own filters to get a single entity back if needed. I wonder what happens if we apply this to a normal arrow, let’s say z = x → y. Assuming nothing about x and y, we get:
tgt(src(arrows_in(x)))
= tgt(src([z]))
= tgt([x])
This is the last moment where we can assume nothing about x. If it were a knot or a tether, the result would be tgt(src(arrows_in(x))) = [x], meaning that this operation is at most an identity (and lift into an array). If it’s something else, then we’re pulled into whatever that other end is, usually the tail-end of a mark or arrow.
You’ve just seen the process of reifying new weave functionalities: it’s tiresome typing out tgt(src(arrows_in(·))) so we might just call it something like hop and call it a day! With hop and the biarrow mint above, we’ve potentially overengineered an easier solution, which is a biarrow complex construction such as:
The problem with this is that m and n are separate except through their endpoints. We could tie them with an arrow h = m → n or similar and pass that around, sure, but that would imply a direction between the two (equal) directions, which feels like a hack.
I like that the mint construction somewhat lost its sense of self while keeping it fully recoverable. Here’s what I mean:
other_dir(·) = src(·)
If we pass along m, other_dir(m) = n, and vice versa. Those are dual identities, they are both the source of each other, and both their targets are different! That’s enough to convince me that this is the way to go. We basically took two arrows and tied them together by their sources.
We’ll be adding this precious mint to the shape module, and hop to the traverse module soon, and who knows how essential it might be in the future.






