ginger conditionals errata

This commit is contained in:
Brian Picciano 2021-03-04 17:21:00 -07:00
parent 2387d70266
commit 3f01bac76d

View File

@ -0,0 +1,195 @@
---
title: >-
Conditionals in Ginger, Errata
description: >-
Too clever by half.
series: ginger
tags: tech
---
After publishing the last post in the series I walked away from my computer
feeling that I was very clever and had made a good post. This was incorrect.
To summarize [the previous post][prev], it's not obvious which is the best way
to structure conditionals in a graphical programming language. My favorite
solution looked something like this:
```
in -> } -> } -if-> } -0-> } -add-> out
in -1-> } -> } } } -1-> } -sub-> out
in -0-> } }
}
in -lt-> }
```
Essentially an `if` operator which accepts a value and a boolean, and which has
two output edges. If the boolean is true then the input value is sent along the
first output edge, and if it's false it's sent along the second.
This structure is not possible, given the properties of ginger graphs that have
been laid out in [other posts in the series][other].
## Nodes, Tuples, and Edges
A ginger graph, as it has been presented so far, is composed of these three
elements. A node has a value, and its value is unique to the graph; if two nodes
have the same value then they are the same node. Edges connect two nodes or
tuples together, and have a value and direction. Tuples are, in essence, a node
whose value is its input edges.
The `if` operation above lies on an edge, not a node or tuple. It cannot have
multiple output edges, since it cannot have any edges at all. It _is_ an edge.
So it's back to the drawing board, to some extent. But luckily I've got some
more ideas in my back pocket.
## Forks and Junctions
In an older conception of ginger there was no tuple, but instead there were
forks and junctions. A junction was essentially the same as a tuple, just named
differently: a node whose value is its input edges. A fork was just the
opposite, a node whose value is its output edges. Junctions and forks naturally
complimented each other, but ultimately I didn't find forks to be useful for
much because there weren't cases where it was necessary to have a single edge be
split across multiple output edges directly; any case which appeared to require
a fork could be satisfied by directing the edge into a 1-tuple and using the
output edges of the 1-tuple.
But now we have such a case. The 1-tuple won't work, because the `if` operator
would only see the 1-tuple, not its edges. It could be supposed that the graph
interpreter could say that an `if` operation must be followed by a 1-tuple, and
that the 1-tuple's output edges have a special meaning in that circumstance. But
making the output edges of a 1-tuple have different meaning in different
circumstances isn't very elegant.
So a fork might be just the thing here. For the example I will represent a
fork as the opposite of a tuple: a vertical column of `{` characters.
```
in -> } -> } -if-> { -0-> } -add-> out
in -1-> } -> } } { -1-> } -sub-> out
in -0-> } }
}
in -lt-> }
```
It _looks_ elegant, which is nice. I am curious though if there's any other
possible use-case where a fork might be useful... if there's not then it seems
odd to introduce an entire new element just to support a single operation. Why
not just make that operation itself the new element?
## Switch it Up
In most conceptions of a flowchart that I've seen a conditional is usually
represented as a node with a different shape than the other nodes (often a
diamond). Ginger could borrow this idea for itself, and declare a new graph
element, alongside nodes, tuples, and edges, called a switch.
Let's say a switch is simply represented by a `-<>`, and acts like a node in all
aspects except that it has no value and is not unique to the graph.
The example presented in the [previous post][prev] would look something like
this:
```
in -> } -> } -<> -0-> } -add-> out
in -1-> } -> } } -1-> } -sub-> out
in -0-> } }
}
in -lt-> }
```
This isn't the _worst_. Like the fork it's adding a new element, but that
element's existence is required and its usage is very specific to that
requirement, whereas the fork's existence is required but ambiguously useful
outside of that requirement.
On the other hand, there are macros to consider...
## Macrophillic
Ginger will certainly support macros, and as alluded to in the last post I'd
like even conditional operations to be fair game for those who want to construct
their own more complex operators. In the context of the switch `-<>` element,
would someone be able to create something like a pattern matching conditional?
If the builtin conditional is implemented as a new graph element then it seems
that the primary way to implement a custom conditional macro will also involve a
new graph element.
While I'm not flat out opposed to allowing for custom graph elements, I'm
extremely skeptical that it's necessary, and would like it to be proven
necessary before considering it. So if we can have a basic conditional, _and_
custom conditional macros built on top of the same broadly useful element, that
seems like the better strategy.
So all of that said, it seems I'm leaning towards forks as the better strategy
in this. But I'd like a different name. "Fork" was nice as being the compliment
of a "junction", but I like "tuple" way more than "junction" because the term
applies well both to the structural element _and_ to the transformation that
element performs (i.e. a tuple element combines its input edges' values into a
tuple value). But "tuple" and "fork" seem weird together...
## Many Minutes Later...
A brief search of the internet reveals no better word than "fork". A place
where a tree's trunk splits into two separate trunks is called a "fork". A
place where a river splits into two separate rivers is called a "fork".
Similarly with roads. And that _is_ what's happening, from the point of view of
the graph's structure: it is an element whose only purpose is to denote multiple
outward edges.
So "fork" it is.
## Other considerations
A 1-tuple is interesting in that it acts essentially as a concatenation of two
edges. A 1-fork could, theoretically, do the same thing:
```
a -foo-> } -bar-> b
c -far-> { -boo-> d
```
The top uses a tuple, the bottom a fork. Each is, conceptually, valid, but I
don't like that two different elements can be used for the exact same use-case.
A 1-tuple is an established concept in data structures, so I am loath to give it
up. A 1-fork, on the other hand, doesn't make sense structurally (would you
point to any random point on a river and call it a "1-fork"?), and fork as a
whole doesn't really have any analog in the realm of data structures. So I'm
prepared to declare 1-forks invalid from the viewpoint of the language
interpreter.
Another consideration: I already expect that there's going to be confusion as to
when to use a fork and when to use multiple outputs from a node. For example,
here's a graph which uses a fork:
```
a -> { -op1-> foo
{ -op2-> bar
```
and here's a graph which has multiple outputs from the same node:
```
a -op1-> foo
-op2-> bar
```
Each could be interpreted to mean the same thing: "set `foo` to the result of
passing `a` into `op1`, and set `bar` to the result of passing `a` into `op2`."
As with the 1-tuple vs 1-fork issue, we have another case where the same
task might be accomplished with two different patterns. This case is trickier
though, and I don't have as confident an answer.
I think an interim rule which could be put in place, subject to review later, is
that multiple edges from a node or tuple indicate that that same value is being
used for multiple operations, while a fork indicates something specific to the
operation on its input edge. It's not a pretty rule, but I think it will do.
Stay tuned for next week when I realize that actually all of this is wrong and
we start over again!
[prev]: {% post_url 2021-03-01-conditionals-in-ginger %}
[other]: {% post_url 2021-01-09-ginger %}